@multiplatform.one/web3 7.24.0 → 7.26.0

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.
Files changed (363) hide show
  1. package/README.md +33 -0
  2. package/dist/cjs/bridge/AuthBridge.cjs +124 -0
  3. package/dist/cjs/bridge/AuthBridge.native.js +186 -0
  4. package/dist/cjs/bridge/AuthBridge.native.js.map +1 -0
  5. package/dist/cjs/bridge/BridgeClient.cjs +12 -9
  6. package/dist/cjs/bridge/BridgeClient.native.js +17 -12
  7. package/dist/cjs/bridge/BridgeClient.native.js.map +1 -1
  8. package/dist/cjs/bridge/BridgeHost.cjs +11 -3
  9. package/dist/cjs/bridge/BridgeHost.native.js +11 -3
  10. package/dist/cjs/bridge/BridgeHost.native.js.map +1 -1
  11. package/dist/cjs/bridge/GpgBridge.cjs +61 -0
  12. package/dist/cjs/bridge/GpgBridge.native.js +111 -0
  13. package/dist/cjs/bridge/GpgBridge.native.js.map +1 -0
  14. package/dist/cjs/bridge/SuiBridge.cjs +51 -0
  15. package/dist/cjs/bridge/SuiBridge.native.js +99 -0
  16. package/dist/cjs/bridge/SuiBridge.native.js.map +1 -0
  17. package/dist/cjs/bridge/authTypes.cjs +28 -0
  18. package/dist/cjs/bridge/authTypes.native.js +31 -0
  19. package/dist/cjs/bridge/authTypes.native.js.map +1 -0
  20. package/dist/cjs/bridge/core.cjs +65 -0
  21. package/dist/cjs/bridge/core.native.js +68 -0
  22. package/dist/cjs/bridge/core.native.js.map +1 -0
  23. package/dist/cjs/bridge/hosting.cjs +91 -0
  24. package/dist/cjs/bridge/hosting.native.js +117 -0
  25. package/dist/cjs/bridge/hosting.native.js.map +1 -0
  26. package/dist/cjs/bridge/index.cjs +3 -17
  27. package/dist/cjs/bridge/index.native.js +2 -16
  28. package/dist/cjs/bridge/index.native.js.map +1 -1
  29. package/dist/cjs/bridge/nativeHost.cjs +91 -0
  30. package/dist/cjs/bridge/nativeHost.native.js +118 -0
  31. package/dist/cjs/bridge/nativeHost.native.js.map +1 -0
  32. package/dist/cjs/bridge/protocol.cjs +30 -1
  33. package/dist/cjs/bridge/protocol.native.js +147 -1
  34. package/dist/cjs/bridge/protocol.native.js.map +1 -1
  35. package/dist/cjs/core/family.cjs +88 -0
  36. package/dist/cjs/core/family.native.js +115 -0
  37. package/dist/cjs/core/family.native.js.map +1 -0
  38. package/dist/cjs/core/index.cjs +41 -0
  39. package/dist/cjs/core/index.native.js +44 -0
  40. package/dist/cjs/core/index.native.js.map +1 -0
  41. package/dist/cjs/core/message.cjs +89 -0
  42. package/dist/cjs/core/message.native.js +104 -0
  43. package/dist/cjs/core/message.native.js.map +1 -0
  44. package/dist/cjs/core/names.cjs +51 -0
  45. package/dist/cjs/core/names.native.js +95 -0
  46. package/dist/cjs/core/names.native.js.map +1 -0
  47. package/dist/cjs/core/react.cjs +48 -0
  48. package/dist/cjs/core/react.native.js +52 -0
  49. package/dist/cjs/core/react.native.js.map +1 -0
  50. package/dist/cjs/core/store.cjs +450 -0
  51. package/dist/cjs/core/store.native.js +637 -0
  52. package/dist/cjs/core/store.native.js.map +1 -0
  53. package/dist/cjs/core/types.cjs +18 -0
  54. package/dist/cjs/core/types.native.js +21 -0
  55. package/dist/cjs/core/types.native.js.map +1 -0
  56. package/dist/cjs/index.cjs +3 -1
  57. package/dist/cjs/index.native.js +2 -0
  58. package/dist/cjs/index.native.js.map +1 -1
  59. package/dist/cjs/providers/WalletProvider.cjs +2 -0
  60. package/dist/cjs/providers/WalletProvider.native.js +123 -90
  61. package/dist/cjs/providers/WalletProvider.native.js.map +1 -1
  62. package/dist/cjs/providers/index.cjs +1 -0
  63. package/dist/cjs/providers/index.native.js +1 -0
  64. package/dist/cjs/providers/index.native.js.map +1 -1
  65. package/dist/cjs/providers/native/deepLink.cjs +40 -23
  66. package/dist/cjs/providers/native/deepLink.native.js +43 -25
  67. package/dist/cjs/providers/native/deepLink.native.js.map +1 -1
  68. package/dist/cjs/providers/native/inject.cjs +2 -4
  69. package/dist/cjs/providers/native/inject.native.js +2 -4
  70. package/dist/cjs/providers/native/inject.native.js.map +1 -1
  71. package/dist/cjs/providers/walletBridge.cjs +35 -0
  72. package/dist/cjs/providers/walletBridge.native.js +38 -0
  73. package/dist/cjs/providers/walletBridge.native.js.map +1 -0
  74. package/dist/cjs/providers/walletConfig.native.js.map +1 -1
  75. package/dist/esm/bridge/AuthBridge.mjs +99 -0
  76. package/dist/esm/bridge/AuthBridge.mjs.map +1 -0
  77. package/dist/esm/bridge/AuthBridge.native.js +158 -0
  78. package/dist/esm/bridge/AuthBridge.native.js.map +1 -0
  79. package/dist/esm/bridge/BridgeClient.mjs +13 -10
  80. package/dist/esm/bridge/BridgeClient.mjs.map +1 -1
  81. package/dist/esm/bridge/BridgeClient.native.js +18 -13
  82. package/dist/esm/bridge/BridgeClient.native.js.map +1 -1
  83. package/dist/esm/bridge/BridgeHost.mjs +12 -4
  84. package/dist/esm/bridge/BridgeHost.mjs.map +1 -1
  85. package/dist/esm/bridge/BridgeHost.native.js +12 -4
  86. package/dist/esm/bridge/BridgeHost.native.js.map +1 -1
  87. package/dist/esm/bridge/GpgBridge.mjs +36 -0
  88. package/dist/esm/bridge/GpgBridge.mjs.map +1 -0
  89. package/dist/esm/bridge/GpgBridge.native.js +83 -0
  90. package/dist/esm/bridge/GpgBridge.native.js.map +1 -0
  91. package/dist/esm/bridge/SuiBridge.mjs +26 -0
  92. package/dist/esm/bridge/SuiBridge.mjs.map +1 -0
  93. package/dist/esm/bridge/SuiBridge.native.js +71 -0
  94. package/dist/esm/bridge/SuiBridge.native.js.map +1 -0
  95. package/dist/esm/bridge/authTypes.mjs +3 -0
  96. package/dist/esm/bridge/authTypes.mjs.map +1 -0
  97. package/dist/esm/bridge/authTypes.native.js +3 -0
  98. package/dist/esm/bridge/authTypes.native.js.map +1 -0
  99. package/dist/esm/bridge/core.mjs +17 -0
  100. package/dist/esm/bridge/core.mjs.map +1 -0
  101. package/dist/esm/bridge/core.native.js +17 -0
  102. package/dist/esm/bridge/core.native.js.map +1 -0
  103. package/dist/esm/bridge/hosting.mjs +62 -0
  104. package/dist/esm/bridge/hosting.mjs.map +1 -0
  105. package/dist/esm/bridge/hosting.native.js +85 -0
  106. package/dist/esm/bridge/hosting.native.js.map +1 -0
  107. package/dist/esm/bridge/index.mjs +2 -9
  108. package/dist/esm/bridge/index.mjs.map +1 -1
  109. package/dist/esm/bridge/index.native.js +2 -9
  110. package/dist/esm/bridge/index.native.js.map +1 -1
  111. package/dist/esm/bridge/nativeHost.mjs +65 -0
  112. package/dist/esm/bridge/nativeHost.mjs.map +1 -0
  113. package/dist/esm/bridge/nativeHost.native.js +89 -0
  114. package/dist/esm/bridge/nativeHost.native.js.map +1 -0
  115. package/dist/esm/bridge/protocol.mjs +28 -2
  116. package/dist/esm/bridge/protocol.mjs.map +1 -1
  117. package/dist/esm/bridge/protocol.native.js +145 -2
  118. package/dist/esm/bridge/protocol.native.js.map +1 -1
  119. package/dist/esm/core/family.mjs +62 -0
  120. package/dist/esm/core/family.mjs.map +1 -0
  121. package/dist/esm/core/family.native.js +86 -0
  122. package/dist/esm/core/family.native.js.map +1 -0
  123. package/dist/esm/core/index.mjs +7 -0
  124. package/dist/esm/core/index.mjs.map +1 -0
  125. package/dist/esm/core/index.native.js +7 -0
  126. package/dist/esm/core/index.native.js.map +1 -0
  127. package/dist/esm/core/message.mjs +63 -0
  128. package/dist/esm/core/message.mjs.map +1 -0
  129. package/dist/esm/core/message.native.js +75 -0
  130. package/dist/esm/core/message.native.js.map +1 -0
  131. package/dist/esm/core/names.mjs +25 -0
  132. package/dist/esm/core/names.mjs.map +1 -0
  133. package/dist/esm/core/names.native.js +66 -0
  134. package/dist/esm/core/names.native.js.map +1 -0
  135. package/dist/esm/core/react.mjs +21 -0
  136. package/dist/esm/core/react.mjs.map +1 -0
  137. package/dist/esm/core/react.native.js +22 -0
  138. package/dist/esm/core/react.native.js.map +1 -0
  139. package/dist/esm/core/store.mjs +424 -0
  140. package/dist/esm/core/store.mjs.map +1 -0
  141. package/dist/esm/core/store.native.js +608 -0
  142. package/dist/esm/core/store.native.js.map +1 -0
  143. package/dist/esm/core/types.mjs +2 -0
  144. package/dist/esm/core/types.mjs.map +1 -0
  145. package/dist/esm/core/types.native.js +2 -0
  146. package/dist/esm/core/types.native.js.map +1 -0
  147. package/dist/esm/index.mjs +3 -2
  148. package/dist/esm/index.mjs.map +1 -1
  149. package/dist/esm/index.native.js +3 -2
  150. package/dist/esm/index.native.js.map +1 -1
  151. package/dist/esm/providers/WalletProvider.mjs +2 -1
  152. package/dist/esm/providers/WalletProvider.mjs.map +1 -1
  153. package/dist/esm/providers/WalletProvider.native.js +125 -93
  154. package/dist/esm/providers/WalletProvider.native.js.map +1 -1
  155. package/dist/esm/providers/index.mjs +2 -2
  156. package/dist/esm/providers/index.mjs.map +1 -1
  157. package/dist/esm/providers/index.native.js +2 -2
  158. package/dist/esm/providers/index.native.js.map +1 -1
  159. package/dist/esm/providers/native/deepLink.mjs +36 -24
  160. package/dist/esm/providers/native/deepLink.mjs.map +1 -1
  161. package/dist/esm/providers/native/deepLink.native.js +39 -26
  162. package/dist/esm/providers/native/deepLink.native.js.map +1 -1
  163. package/dist/esm/providers/native/inject.mjs +1 -3
  164. package/dist/esm/providers/native/inject.mjs.map +1 -1
  165. package/dist/esm/providers/native/inject.native.js +1 -3
  166. package/dist/esm/providers/native/inject.native.js.map +1 -1
  167. package/dist/esm/providers/walletBridge.mjs +9 -0
  168. package/dist/esm/providers/walletBridge.mjs.map +1 -0
  169. package/dist/esm/providers/walletBridge.native.js +9 -0
  170. package/dist/esm/providers/walletBridge.native.js.map +1 -0
  171. package/dist/esm/providers/walletConfig.mjs.map +1 -1
  172. package/dist/esm/providers/walletConfig.native.js.map +1 -1
  173. package/dist/jsx/bridge/AuthBridge.mjs +99 -0
  174. package/dist/jsx/bridge/AuthBridge.mjs.map +1 -0
  175. package/dist/jsx/bridge/AuthBridge.native.js +186 -0
  176. package/dist/jsx/bridge/AuthBridge.native.js.map +1 -0
  177. package/dist/jsx/bridge/BridgeClient.mjs +13 -10
  178. package/dist/jsx/bridge/BridgeClient.mjs.map +1 -1
  179. package/dist/jsx/bridge/BridgeClient.native.js +17 -12
  180. package/dist/jsx/bridge/BridgeClient.native.js.map +1 -1
  181. package/dist/jsx/bridge/BridgeHost.mjs +12 -4
  182. package/dist/jsx/bridge/BridgeHost.mjs.map +1 -1
  183. package/dist/jsx/bridge/BridgeHost.native.js +11 -3
  184. package/dist/jsx/bridge/BridgeHost.native.js.map +1 -1
  185. package/dist/jsx/bridge/GpgBridge.mjs +36 -0
  186. package/dist/jsx/bridge/GpgBridge.mjs.map +1 -0
  187. package/dist/jsx/bridge/GpgBridge.native.js +111 -0
  188. package/dist/jsx/bridge/GpgBridge.native.js.map +1 -0
  189. package/dist/jsx/bridge/SuiBridge.mjs +26 -0
  190. package/dist/jsx/bridge/SuiBridge.mjs.map +1 -0
  191. package/dist/jsx/bridge/SuiBridge.native.js +99 -0
  192. package/dist/jsx/bridge/SuiBridge.native.js.map +1 -0
  193. package/dist/jsx/bridge/authTypes.mjs +3 -0
  194. package/dist/jsx/bridge/authTypes.mjs.map +1 -0
  195. package/dist/jsx/bridge/authTypes.native.js +31 -0
  196. package/dist/jsx/bridge/authTypes.native.js.map +1 -0
  197. package/dist/jsx/bridge/core.mjs +17 -0
  198. package/dist/jsx/bridge/core.mjs.map +1 -0
  199. package/dist/jsx/bridge/core.native.js +68 -0
  200. package/dist/jsx/bridge/core.native.js.map +1 -0
  201. package/dist/jsx/bridge/hosting.mjs +62 -0
  202. package/dist/jsx/bridge/hosting.mjs.map +1 -0
  203. package/dist/jsx/bridge/hosting.native.js +117 -0
  204. package/dist/jsx/bridge/hosting.native.js.map +1 -0
  205. package/dist/jsx/bridge/index.mjs +2 -9
  206. package/dist/jsx/bridge/index.mjs.map +1 -1
  207. package/dist/jsx/bridge/index.native.js +2 -16
  208. package/dist/jsx/bridge/index.native.js.map +1 -1
  209. package/dist/jsx/bridge/nativeHost.mjs +65 -0
  210. package/dist/jsx/bridge/nativeHost.mjs.map +1 -0
  211. package/dist/jsx/bridge/nativeHost.native.js +118 -0
  212. package/dist/jsx/bridge/nativeHost.native.js.map +1 -0
  213. package/dist/jsx/bridge/protocol.mjs +28 -2
  214. package/dist/jsx/bridge/protocol.mjs.map +1 -1
  215. package/dist/jsx/bridge/protocol.native.js +147 -1
  216. package/dist/jsx/bridge/protocol.native.js.map +1 -1
  217. package/dist/jsx/core/family.mjs +62 -0
  218. package/dist/jsx/core/family.mjs.map +1 -0
  219. package/dist/jsx/core/family.native.js +115 -0
  220. package/dist/jsx/core/family.native.js.map +1 -0
  221. package/dist/jsx/core/index.mjs +7 -0
  222. package/dist/jsx/core/index.mjs.map +1 -0
  223. package/dist/jsx/core/index.native.js +44 -0
  224. package/dist/jsx/core/index.native.js.map +1 -0
  225. package/dist/jsx/core/message.mjs +63 -0
  226. package/dist/jsx/core/message.mjs.map +1 -0
  227. package/dist/jsx/core/message.native.js +104 -0
  228. package/dist/jsx/core/message.native.js.map +1 -0
  229. package/dist/jsx/core/names.mjs +25 -0
  230. package/dist/jsx/core/names.mjs.map +1 -0
  231. package/dist/jsx/core/names.native.js +95 -0
  232. package/dist/jsx/core/names.native.js.map +1 -0
  233. package/dist/jsx/core/react.mjs +21 -0
  234. package/dist/jsx/core/react.mjs.map +1 -0
  235. package/dist/jsx/core/react.native.js +52 -0
  236. package/dist/jsx/core/react.native.js.map +1 -0
  237. package/dist/jsx/core/store.mjs +424 -0
  238. package/dist/jsx/core/store.mjs.map +1 -0
  239. package/dist/jsx/core/store.native.js +637 -0
  240. package/dist/jsx/core/store.native.js.map +1 -0
  241. package/dist/jsx/core/types.mjs +2 -0
  242. package/dist/jsx/core/types.mjs.map +1 -0
  243. package/dist/jsx/core/types.native.js +21 -0
  244. package/dist/jsx/core/types.native.js.map +1 -0
  245. package/dist/jsx/index.js +3 -2
  246. package/dist/jsx/index.js.map +1 -1
  247. package/dist/jsx/index.mjs +3 -2
  248. package/dist/jsx/index.mjs.map +1 -1
  249. package/dist/jsx/index.native.js +2 -0
  250. package/dist/jsx/index.native.js.map +1 -1
  251. package/dist/jsx/providers/WalletProvider.mjs +2 -1
  252. package/dist/jsx/providers/WalletProvider.mjs.map +1 -1
  253. package/dist/jsx/providers/WalletProvider.native.js +123 -90
  254. package/dist/jsx/providers/WalletProvider.native.js.map +1 -1
  255. package/dist/jsx/providers/index.mjs +2 -2
  256. package/dist/jsx/providers/index.mjs.map +1 -1
  257. package/dist/jsx/providers/index.native.js +1 -0
  258. package/dist/jsx/providers/index.native.js.map +1 -1
  259. package/dist/jsx/providers/native/deepLink.mjs +36 -24
  260. package/dist/jsx/providers/native/deepLink.mjs.map +1 -1
  261. package/dist/jsx/providers/native/deepLink.native.js +43 -25
  262. package/dist/jsx/providers/native/deepLink.native.js.map +1 -1
  263. package/dist/jsx/providers/native/inject.mjs +1 -3
  264. package/dist/jsx/providers/native/inject.mjs.map +1 -1
  265. package/dist/jsx/providers/native/inject.native.js +2 -4
  266. package/dist/jsx/providers/native/inject.native.js.map +1 -1
  267. package/dist/jsx/providers/walletBridge.mjs +9 -0
  268. package/dist/jsx/providers/walletBridge.mjs.map +1 -0
  269. package/dist/jsx/providers/walletBridge.native.js +38 -0
  270. package/dist/jsx/providers/walletBridge.native.js.map +1 -0
  271. package/dist/jsx/providers/walletConfig.mjs.map +1 -1
  272. package/dist/jsx/providers/walletConfig.native.js.map +1 -1
  273. package/docs/bridge-protocol.md +485 -0
  274. package/package.json +14 -6
  275. package/src/bridge/AuthBridge.ts +146 -0
  276. package/src/bridge/BridgeClient.spec.ts +12 -0
  277. package/src/bridge/BridgeClient.ts +15 -10
  278. package/src/bridge/BridgeHost.spec.ts +20 -4
  279. package/src/bridge/BridgeHost.ts +16 -5
  280. package/src/bridge/GpgBridge.ts +49 -0
  281. package/src/bridge/SuiBridge.ts +34 -0
  282. package/src/bridge/authTypes.ts +76 -0
  283. package/src/bridge/core.ts +42 -0
  284. package/src/bridge/hosting.spec.ts +98 -0
  285. package/src/bridge/hosting.ts +99 -0
  286. package/src/bridge/index.ts +1 -8
  287. package/src/bridge/nativeHost.spec.ts +165 -0
  288. package/src/bridge/nativeHost.ts +120 -0
  289. package/src/bridge/protocol.spec.ts +24 -1
  290. package/src/bridge/protocol.ts +44 -1
  291. package/src/core/family.spec.ts +117 -0
  292. package/src/core/family.ts +103 -0
  293. package/src/core/imports.spec.ts +82 -0
  294. package/src/core/index.ts +47 -0
  295. package/src/core/message.spec.ts +178 -0
  296. package/src/core/message.ts +119 -0
  297. package/src/core/names.ts +29 -0
  298. package/src/core/react.spec.tsx +75 -0
  299. package/src/core/react.ts +34 -0
  300. package/src/core/store.spec.ts +890 -0
  301. package/src/core/store.ts +558 -0
  302. package/src/core/types.ts +96 -0
  303. package/src/index.ts +6 -0
  304. package/src/providers/WalletProvider.native.tsx +139 -85
  305. package/src/providers/WalletProvider.tsx +1 -1
  306. package/src/providers/index.ts +2 -0
  307. package/src/providers/native/deepLink.spec.ts +73 -1
  308. package/src/providers/native/deepLink.ts +76 -28
  309. package/src/providers/native/inject.ts +1 -9
  310. package/src/providers/walletBridge.ts +24 -0
  311. package/src/providers/walletConfig.ts +15 -1
  312. package/types/bridge/AuthBridge.d.ts +58 -0
  313. package/types/bridge/AuthBridge.d.ts.map +1 -0
  314. package/types/bridge/BridgeClient.d.ts +3 -1
  315. package/types/bridge/BridgeClient.d.ts.map +1 -1
  316. package/types/bridge/BridgeHost.d.ts +1 -1
  317. package/types/bridge/BridgeHost.d.ts.map +1 -1
  318. package/types/bridge/GpgBridge.d.ts +23 -0
  319. package/types/bridge/GpgBridge.d.ts.map +1 -0
  320. package/types/bridge/SuiBridge.d.ts +18 -0
  321. package/types/bridge/SuiBridge.d.ts.map +1 -0
  322. package/types/bridge/authTypes.d.ts +71 -0
  323. package/types/bridge/authTypes.d.ts.map +1 -0
  324. package/types/bridge/core.d.ts +16 -0
  325. package/types/bridge/core.d.ts.map +1 -0
  326. package/types/bridge/hosting.d.ts +34 -0
  327. package/types/bridge/hosting.d.ts.map +1 -0
  328. package/types/bridge/index.d.ts +1 -8
  329. package/types/bridge/index.d.ts.map +1 -1
  330. package/types/bridge/nativeHost.d.ts +55 -0
  331. package/types/bridge/nativeHost.d.ts.map +1 -0
  332. package/types/bridge/protocol.d.ts +34 -1
  333. package/types/bridge/protocol.d.ts.map +1 -1
  334. package/types/core/family.d.ts +44 -0
  335. package/types/core/family.d.ts.map +1 -0
  336. package/types/core/index.d.ts +7 -0
  337. package/types/core/index.d.ts.map +1 -0
  338. package/types/core/message.d.ts +44 -0
  339. package/types/core/message.d.ts.map +1 -0
  340. package/types/core/names.d.ts +7 -0
  341. package/types/core/names.d.ts.map +1 -0
  342. package/types/core/react.d.ts +14 -0
  343. package/types/core/react.d.ts.map +1 -0
  344. package/types/core/store.d.ts +64 -0
  345. package/types/core/store.d.ts.map +1 -0
  346. package/types/core/types.d.ts +88 -0
  347. package/types/core/types.d.ts.map +1 -0
  348. package/types/index.d.ts +2 -1
  349. package/types/index.d.ts.map +1 -1
  350. package/types/providers/WalletProvider.d.ts +1 -0
  351. package/types/providers/WalletProvider.d.ts.map +1 -1
  352. package/types/providers/WalletProvider.native.d.ts +2 -1
  353. package/types/providers/WalletProvider.native.d.ts.map +1 -1
  354. package/types/providers/index.d.ts +1 -1
  355. package/types/providers/index.d.ts.map +1 -1
  356. package/types/providers/native/deepLink.d.ts +29 -4
  357. package/types/providers/native/deepLink.d.ts.map +1 -1
  358. package/types/providers/native/inject.d.ts +1 -7
  359. package/types/providers/native/inject.d.ts.map +1 -1
  360. package/types/providers/walletBridge.d.ts +17 -0
  361. package/types/providers/walletBridge.d.ts.map +1 -0
  362. package/types/providers/walletConfig.d.ts +18 -1
  363. package/types/providers/walletConfig.d.ts.map +1 -1
@@ -0,0 +1,165 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { BridgeHost } from "./BridgeHost";
3
+ import { buildInjectScript, createNativeBridge } from "./nativeHost";
4
+ import { createPostMessageTransport } from "./postMessageTransport";
5
+ import { BRIDGE_PROTOCOL_VERSION } from "./protocol";
6
+
7
+ /**
8
+ * A fake page: `inject` runs the native side's script against a fake window
9
+ * whose `__bridgeReceive` feeds a BridgeHost, and the host's replies come back
10
+ * through `native.receive` with the page's URL.
11
+ */
12
+ function setup(pageUrl = "https://wallet.example.com/webview.html") {
13
+ const injected: string[] = [];
14
+ const win: { location: { origin: string }; __bridgeReceive?: (raw: string) => void } = {
15
+ location: { origin: new URL(pageUrl).origin },
16
+ };
17
+ let postedFrom = pageUrl;
18
+ const native = createNativeBridge({
19
+ pageUrl,
20
+ allowDevHosts: true,
21
+ inject(script) {
22
+ injected.push(script);
23
+ new Function("window", script)(win);
24
+ },
25
+ });
26
+ const transport = createPostMessageTransport((raw) => native.receive(raw, postedFrom));
27
+ win.__bridgeReceive = (raw) => transport.receive(raw);
28
+ const host = new BridgeHost(transport);
29
+ return {
30
+ native,
31
+ host,
32
+ injected,
33
+ win,
34
+ postFrom(url: string) {
35
+ postedFrom = url;
36
+ },
37
+ };
38
+ }
39
+
40
+ describe("createNativeBridge", () => {
41
+ it("refuses a page that is not hosted", () => {
42
+ expect(() =>
43
+ createNativeBridge({ pageUrl: "file:///webview.html", inject: () => undefined }),
44
+ ).toThrow(/https/);
45
+ });
46
+
47
+ it("refuses a plain-http dev page unless dev hosts are allowed", () => {
48
+ const pageUrl = "http://localhost:8099/webview.html";
49
+ expect(() => createNativeBridge({ pageUrl, inject: () => undefined })).toThrow(
50
+ /development builds/,
51
+ );
52
+ expect(
53
+ createNativeBridge({ pageUrl, inject: () => undefined, allowDevHosts: true }).pageOrigin,
54
+ ).toBe("http://localhost:8099");
55
+ });
56
+
57
+ it("queues requests until sys.ready, then flushes them", async () => {
58
+ const { native, host, injected } = setup();
59
+ host.register("evm", (method) => (method === "eth_chainId" ? "0x1" : null));
60
+ const pending = native.client.request("evm", "eth_chainId");
61
+ expect(injected).toHaveLength(0);
62
+ host.ready();
63
+ await expect(pending).resolves.toBe("0x1");
64
+ expect(injected).toHaveLength(1);
65
+ expect(native.peer).toEqual({ protocol: BRIDGE_PROTOCOL_VERSION, namespaces: ["evm", "sys"] });
66
+ });
67
+
68
+ it("drops messages posted by a page on another origin", async () => {
69
+ const { native, host, postFrom } = setup();
70
+ host.register("evm", () => "0x1");
71
+ postFrom("https://evil.example/");
72
+ host.ready();
73
+ expect(native.peer).toBeNull();
74
+ postFrom("https://wallet.example.com/other");
75
+ host.ready();
76
+ expect(native.peer).not.toBeNull();
77
+ });
78
+
79
+ it("drops messages that name no sender", () => {
80
+ const { native, postFrom, host } = setup();
81
+ postFrom("");
82
+ host.ready();
83
+ expect(native.peer).toBeNull();
84
+ });
85
+
86
+ it("delivers nothing into a document on another origin", async () => {
87
+ const { native, host, injected, win } = setup();
88
+ host.register("evm", () => "0x1");
89
+ host.ready();
90
+ win.location.origin = "https://evil.example";
91
+ const lost = native.client.request("evm", "eth_chainId", undefined, { timeoutMs: 20 });
92
+ expect(injected).toHaveLength(1);
93
+ await expect(lost).rejects.toThrow();
94
+ });
95
+
96
+ it("a top-frame load on another origin fails what is in flight and stops delivery", async () => {
97
+ const { native, host, injected } = setup();
98
+ host.register("evm", (method) =>
99
+ method === "eth_chainId" ? "0x1" : new Promise(() => undefined),
100
+ );
101
+ host.ready();
102
+ const lost = native.client.request("evm", "eth_requestAccounts");
103
+ expect(native.navigated("https://wallet.example.com/webview.html")).toBe(false);
104
+ expect(native.navigated("about:blank")).toBe(false);
105
+ expect(native.peer).not.toBeNull();
106
+ expect(native.navigated("https://evil.example/form")).toBe(true);
107
+ await expect(lost).rejects.toThrow(/went away/);
108
+ expect(native.peer).toBeNull();
109
+ const before = injected.length;
110
+ const next = native.client.request("evm", "eth_chainId");
111
+ expect(injected).toHaveLength(before);
112
+ host.ready();
113
+ await expect(next).resolves.toBe("0x1");
114
+ });
115
+
116
+ it("fails requests in flight when the page announces itself again", async () => {
117
+ const { native, host } = setup();
118
+ host.register("evm", () => new Promise(() => undefined));
119
+ host.ready();
120
+ const lost = native.client.request("evm", "eth_requestAccounts");
121
+ host.ready();
122
+ await expect(lost).rejects.toThrow(/reloaded/);
123
+ });
124
+
125
+ it("pageGone fails what is in flight and queues until the next ready", async () => {
126
+ const { native, host, injected } = setup();
127
+ host.register("evm", (method) =>
128
+ method === "eth_chainId" ? "0x1" : new Promise(() => undefined),
129
+ );
130
+ host.ready();
131
+ const lost = native.client.request("evm", "eth_requestAccounts");
132
+ native.pageGone();
133
+ await expect(lost).rejects.toThrow(/went away/);
134
+ const before = injected.length;
135
+ const next = native.client.request("evm", "eth_chainId");
136
+ expect(injected).toHaveLength(before);
137
+ host.ready();
138
+ await expect(next).resolves.toBe("0x1");
139
+ });
140
+
141
+ it("asks for the page back twice at most until it announces itself again", () => {
142
+ const { native, host } = setup();
143
+ host.ready();
144
+ expect(native.navigated("https://evil.example/")).toBe(true);
145
+ expect(native.navigated("https://evil.example/")).toBe(true);
146
+ expect(native.navigated("https://evil.example/")).toBe(false);
147
+ host.ready();
148
+ expect(native.navigated("https://evil.example/")).toBe(true);
149
+ });
150
+
151
+ it("exposes the page origin", () => {
152
+ expect(setup("http://localhost:8099/webview.html").native.pageOrigin).toBe(
153
+ "http://localhost:8099",
154
+ );
155
+ });
156
+
157
+ it("buildInjectScript survives quotes and newlines", () => {
158
+ const received: string[] = [];
159
+ const raw = 'he said "hi"\nline2\\end </script>';
160
+ new Function("window", buildInjectScript(raw))({
161
+ __bridgeReceive: (s: string) => received.push(s),
162
+ });
163
+ expect(received).toEqual([raw]);
164
+ });
165
+ });
@@ -0,0 +1,120 @@
1
+ import { BridgeClient } from "./BridgeClient";
2
+ import { hostedPageOrigin, parseOrigin } from "./hosting";
3
+ import { createPostMessageTransport } from "./postMessageTransport";
4
+ import { BRIDGE_PROTOCOL_VERSION, type BridgeReady } from "./protocol";
5
+
6
+ export interface NativeBridgeOptions {
7
+ /** The hosted wallet page. Must pass `isHostedPageUrl`. */
8
+ pageUrl: string;
9
+ /** Accept plain http on the development hosts. Pass `__DEV__`; never true in a release build. */
10
+ allowDevHosts?: boolean;
11
+ /** Runs a script in the page: react-native-webview's `injectJavaScript`. */
12
+ inject(script: string): void;
13
+ /** Default request timeout, see BridgeClient. */
14
+ timeoutMs?: number;
15
+ /** Called once the page reports `sys.ready`. */
16
+ onReady?(ready: BridgeReady): void;
17
+ }
18
+
19
+ export interface NativeBridge {
20
+ readonly client: BridgeClient;
21
+ /** Origin of the hosted page; messages from any other origin are dropped. */
22
+ readonly pageOrigin: string;
23
+ /** What the page announced in `sys.ready`, or null before it has. */
24
+ readonly peer: BridgeReady | null;
25
+ /**
26
+ * Feed a WebView message in: `receive(e.nativeEvent.data, e.nativeEvent.url)`.
27
+ * A message whose `fromUrl` is missing or on another origin is dropped.
28
+ */
29
+ receive(data: string, fromUrl: string): void;
30
+ /**
31
+ * The top frame started loading `url` (react-native-webview's `onLoadStart`).
32
+ * A document on another origin is not the page: what is in flight fails,
33
+ * nothing more is delivered until the page announces itself again, and the
34
+ * answer is true so the host loads `pageUrl` back. Twice at most between two
35
+ * `sys.ready`s: a page that never comes back stays down instead of looping.
36
+ */
37
+ navigated(url: string): boolean;
38
+ /**
39
+ * The page died (WebView content process gone) or is being reloaded: fail
40
+ * the requests in flight and queue new ones until the next `sys.ready`.
41
+ */
42
+ pageGone(): void;
43
+ dispose(): void;
44
+ }
45
+
46
+ /**
47
+ * The script that delivers one message into the page. The raw string is
48
+ * embedded with JSON.stringify so quotes, newlines and backslashes survive;
49
+ * the trailing `true;` is react-native-webview's injectJavaScript contract.
50
+ * With `pageOrigin` the script delivers only while the top frame is on that
51
+ * origin, so a document that replaced the page never sees a message.
52
+ */
53
+ export function buildInjectScript(raw: string, pageOrigin?: string): string {
54
+ const guard = pageOrigin
55
+ ? `window.location && window.location.origin === ${JSON.stringify(pageOrigin)} && `
56
+ : "";
57
+ return `${guard}window.__bridgeReceive && window.__bridgeReceive(${JSON.stringify(raw)});true;`;
58
+ }
59
+
60
+ /**
61
+ * The native end of the bridge for any React Native WebView host. Requests
62
+ * wait in a queue until the page says `sys.ready`, because the first request
63
+ * usually fires before the page has loaded.
64
+ */
65
+ export function createNativeBridge(options: NativeBridgeOptions): NativeBridge {
66
+ const pageOrigin = hostedPageOrigin(options.pageUrl, { allowDevHosts: options.allowDevHosts });
67
+ const queue: string[] = [];
68
+ let peer: BridgeReady | null = null;
69
+ let reloads = 0;
70
+
71
+ const transport = createPostMessageTransport((raw) => {
72
+ if (peer) options.inject(buildInjectScript(raw, pageOrigin));
73
+ else queue.push(raw);
74
+ });
75
+ const offReady = transport.subscribe((message) => {
76
+ if (message.ns !== "sys" || message.kind !== "event" || message.method !== "ready") return;
77
+ // A second ready is a new document: whatever the old one was answering is lost.
78
+ if (peer) client.rejectPending(new Error("The wallet page reloaded"));
79
+ reloads = 0;
80
+ const params = message.params as Partial<BridgeReady> | undefined;
81
+ peer = {
82
+ protocol: typeof params?.protocol === "number" ? params.protocol : BRIDGE_PROTOCOL_VERSION,
83
+ namespaces: Array.isArray(params?.namespaces) ? params.namespaces : [],
84
+ };
85
+ for (const raw of queue.splice(0)) options.inject(buildInjectScript(raw, pageOrigin));
86
+ options.onReady?.(peer);
87
+ });
88
+ const client = new BridgeClient(transport, { timeoutMs: options.timeoutMs });
89
+
90
+ const pageGone = () => {
91
+ peer = null;
92
+ queue.length = 0;
93
+ client.rejectPending(new Error("The wallet page went away"));
94
+ };
95
+
96
+ return {
97
+ client,
98
+ pageOrigin,
99
+ get peer() {
100
+ return peer;
101
+ },
102
+ receive(data, fromUrl) {
103
+ if (!fromUrl || parseOrigin(fromUrl)?.origin !== pageOrigin) return;
104
+ transport.receive(data);
105
+ },
106
+ navigated(url) {
107
+ if (url.trim().toLowerCase() === "about:blank") return false;
108
+ if (parseOrigin(url)?.origin === pageOrigin) return false;
109
+ pageGone();
110
+ reloads += 1;
111
+ return reloads <= 2;
112
+ },
113
+ pageGone,
114
+ dispose() {
115
+ offReady();
116
+ client.dispose();
117
+ queue.length = 0;
118
+ },
119
+ };
120
+ }
@@ -1,5 +1,11 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { isBridgeMessage, newRequestId, type BridgeMessage } from "./protocol";
2
+ import {
3
+ BridgeRequestError,
4
+ NAMESPACES,
5
+ isBridgeMessage,
6
+ newRequestId,
7
+ type BridgeMessage,
8
+ } from "./protocol";
3
9
 
4
10
  describe("protocol", () => {
5
11
  it("accepts a well-formed req", () => {
@@ -9,6 +15,23 @@ describe("protocol", () => {
9
15
  it("accepts an event without id", () => {
10
16
  expect(isBridgeMessage({ ns: "solana", kind: "event", method: "connect" })).toBe(true);
11
17
  });
18
+ it("accepts the sui, gpg and auth namespaces", () => {
19
+ expect(NAMESPACES).toEqual(["evm", "solana", "sui", "gpg", "auth", "sys"]);
20
+ for (const ns of ["sui", "gpg", "auth"]) {
21
+ expect(isBridgeMessage({ id: "1", ns, kind: "req", method: "signMessage" })).toBe(true);
22
+ }
23
+ });
24
+ it("BridgeRequestError keeps code and data", () => {
25
+ const e = new BridgeRequestError(-32002, "refused", { status: 401 });
26
+ expect(e).toBeInstanceOf(Error);
27
+ expect([e.name, e.code, e.message, e.data]).toEqual([
28
+ "BridgeRequestError",
29
+ -32002,
30
+ "refused",
31
+ { status: 401 },
32
+ ]);
33
+ expect("data" in new BridgeRequestError(1, "x")).toBe(false);
34
+ });
12
35
  it("rejects unknown namespace", () => {
13
36
  expect(isBridgeMessage({ ns: "btc", kind: "req", method: "x" })).toBe(false);
14
37
  });
@@ -1,12 +1,36 @@
1
- export const NAMESPACES = ["evm", "solana", "sys"] as const;
1
+ /**
2
+ * Version of the wire protocol in docs/bridge-protocol.md. The WebView sends it
3
+ * in `sys.ready`; bump it only for a change an existing host cannot ignore.
4
+ */
5
+ export const BRIDGE_PROTOCOL_VERSION = 1;
6
+
7
+ export const NAMESPACES = ["evm", "solana", "sui", "gpg", "auth", "sys"] as const;
2
8
  export type Namespace = (typeof NAMESPACES)[number];
3
9
 
4
10
  export const KINDS = ["req", "res", "event"] as const;
5
11
  export type Kind = (typeof KINDS)[number];
6
12
 
13
+ /** Error codes a response can carry. EIP-1193 and JSON-RPC codes keep their meaning. */
14
+ export const BridgeErrorCode = {
15
+ userRejected: 4001,
16
+ unsupportedMethod: 4200,
17
+ methodNotFound: -32601,
18
+ invalidParams: -32602,
19
+ internal: -32000,
20
+ /** The page is not served over https (or loopback http), so it cannot sign in. */
21
+ pageNotHosted: -32001,
22
+ /** Keycloak refused the request; `data` carries `status`, `error`, `errorDescription`. */
23
+ keycloakRefused: -32002,
24
+ /** No wallet or signer for the requested family is available in the page. */
25
+ walletUnavailable: -32003,
26
+ /** The account has required actions left and no onboarding client was given. */
27
+ profileIncomplete: -32004,
28
+ } as const;
29
+
7
30
  export interface BridgeError {
8
31
  code: number;
9
32
  message: string;
33
+ data?: unknown;
10
34
  }
11
35
 
12
36
  export interface BridgeMessage {
@@ -20,6 +44,25 @@ export interface BridgeMessage {
20
44
  error?: BridgeError;
21
45
  }
22
46
 
47
+ /** What the WebView announces in `sys.ready`. */
48
+ export interface BridgeReady {
49
+ protocol: number;
50
+ namespaces: Namespace[];
51
+ }
52
+
53
+ /** An error that crosses the bridge with its code and data intact, in either direction. */
54
+ export class BridgeRequestError extends Error {
55
+ readonly code: number;
56
+ readonly data?: unknown;
57
+
58
+ constructor(code: number, message: string, data?: unknown) {
59
+ super(message);
60
+ this.name = "BridgeRequestError";
61
+ this.code = code;
62
+ if (data !== undefined) this.data = data;
63
+ }
64
+ }
65
+
23
66
  export function isBridgeMessage(value: unknown): value is BridgeMessage {
24
67
  if (typeof value !== "object" || value === null) return false;
25
68
  const m = value as Record<string, unknown>;
@@ -0,0 +1,117 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { testDriver } from "../../tests/core/testDriver";
3
+ import { createFamilyRegistry, defineWalletFamily, type WalletFamilyDefinition } from "./family";
4
+
5
+ const message = {
6
+ header: "wants you to sign in with your Solana account:",
7
+ statement: null,
8
+ chainId: "solana:mainnet",
9
+ };
10
+ const driver = () => testDriver("solana", [], {});
11
+ const sol: WalletFamilyDefinition = {
12
+ id: "sol",
13
+ label: "Solana",
14
+ aliases: ["solana"],
15
+ message,
16
+ driver,
17
+ };
18
+ const gpg: WalletFamilyDefinition = {
19
+ id: "gpg",
20
+ label: "GPG",
21
+ message: {
22
+ header: "wants you to sign in with your GPG key.",
23
+ statement: null,
24
+ chainId: null,
25
+ address: false,
26
+ },
27
+ connectionless: true,
28
+ };
29
+
30
+ describe("defineWalletFamily", () => {
31
+ it("returns a frozen family with defaults filled in", () => {
32
+ const family = defineWalletFamily({ id: "sui", label: "Sui", message, driver });
33
+ expect(family).toMatchObject({ id: "sui", label: "Sui", aliases: [], connectionless: false });
34
+ expect(Object.isFrozen(family)).toBe(true);
35
+ expect(Object.isFrozen(family.aliases)).toBe(true);
36
+ expect(Object.isFrozen(family.message)).toBe(true);
37
+ });
38
+
39
+ it("does not share the caller's arrays", () => {
40
+ const aliases = ["solana"];
41
+ const family = defineWalletFamily({ ...sol, aliases });
42
+ aliases.push("other");
43
+ expect(family.aliases).toEqual(["solana"]);
44
+ });
45
+
46
+ it("a connectionless family has no driver", () => {
47
+ expect(defineWalletFamily(gpg).connectionless).toBe(true);
48
+ expect(() => defineWalletFamily({ ...gpg, driver })).toThrow(
49
+ /connectionless.*cannot have a driver/,
50
+ );
51
+ });
52
+
53
+ it("a connectable family needs a driver", () => {
54
+ expect(() => defineWalletFamily({ ...sol, driver: undefined })).toThrow(/needs a driver/);
55
+ });
56
+
57
+ it.each([
58
+ [{ id: "Sol" }, /id must be lowercase/],
59
+ [{ id: "" }, /id must be lowercase/],
60
+ [{ id: "9sol" }, /id must be lowercase/],
61
+ [{ aliases: ["Solana"] }, /alias must be lowercase/],
62
+ [{ aliases: ["sol"] }, /its own id as an alias/],
63
+ [{ label: " " }, /needs a label/],
64
+ [{ message: { ...message, header: "" } }, /message spec with a header/],
65
+ ])("refuses %o", (change, error) => {
66
+ expect(() => defineWalletFamily({ ...sol, ...change } as WalletFamilyDefinition)).toThrow(
67
+ error,
68
+ );
69
+ });
70
+ });
71
+
72
+ describe("createFamilyRegistry", () => {
73
+ const registry = createFamilyRegistry([defineWalletFamily(sol), defineWalletFamily(gpg)]);
74
+
75
+ it("finds a family by id or alias, in the order the app listed them", () => {
76
+ expect(registry.families.map((family) => family.id)).toEqual(["sol", "gpg"]);
77
+ expect(registry.get("sol")?.label).toBe("Solana");
78
+ expect(registry.get("solana")?.id).toBe("sol");
79
+ expect(registry.get("eth")).toBeUndefined();
80
+ });
81
+
82
+ it("require names the registered families when one is missing", () => {
83
+ expect(registry.require("solana").id).toBe("sol");
84
+ expect(() => registry.require("eth")).toThrow(
85
+ "wallet family eth is not registered; registered: sol, gpg",
86
+ );
87
+ expect(() => createFamilyRegistry([]).require("eth")).toThrow(/registered: none/);
88
+ });
89
+
90
+ it("carries each family's message spec", () => {
91
+ expect(registry.messages.families).toEqual(["sol", "gpg"]);
92
+ expect(registry.messages.resolve("solana")).toBe("sol");
93
+ expect(registry.messages.spec("gpg")?.address).toBe(false);
94
+ });
95
+
96
+ it("refuses a family registered twice and a name two families claim", () => {
97
+ expect(() => createFamilyRegistry([defineWalletFamily(sol), defineWalletFamily(sol)])).toThrow(
98
+ /sol is registered twice/,
99
+ );
100
+ expect(() =>
101
+ createFamilyRegistry([
102
+ defineWalletFamily(sol),
103
+ defineWalletFamily({ ...gpg, aliases: ["solana"] }),
104
+ ]),
105
+ ).toThrow(/"solana" is claimed by sol and gpg/);
106
+ expect(() =>
107
+ createFamilyRegistry([
108
+ defineWalletFamily({ ...gpg, aliases: ["sol"] }),
109
+ defineWalletFamily(sol),
110
+ ]),
111
+ ).toThrow(/"sol" is claimed by gpg and sol/);
112
+ });
113
+
114
+ it("the registry's family list cannot be changed", () => {
115
+ expect(Object.isFrozen(registry.families)).toBe(true);
116
+ });
117
+ });
@@ -0,0 +1,103 @@
1
+ import type { ScreenDef } from "@multiplatform.one/connectkit";
2
+ import type { ComponentType, ReactNode } from "react";
3
+ import {
4
+ createMessageRegistry,
5
+ type WalletMessageRegistry,
6
+ type WalletMessageSpec,
7
+ } from "./message";
8
+ import { assertName, nameTable } from "./names";
9
+ import type { ConnectionDriver } from "./types";
10
+
11
+ /** What a family package hands the core: `eth({chains})`, `sol({rpcUrl})`, `gpg()`. */
12
+ export interface WalletFamilyDefinition {
13
+ /** Short family id, the one the Keycloak message names: `eth`, `sol`, `sui`, `btc`, `gpg`. */
14
+ readonly id: string;
15
+ readonly label: string;
16
+ /** Other names callers use for the family, such as the ecosystem name `evm` for `eth`. */
17
+ readonly aliases?: readonly string[];
18
+ readonly message: WalletMessageSpec;
19
+ /**
20
+ * Makes the family's connection driver. The store calls it once, the first
21
+ * time it needs the family, so a family nobody touches starts nothing.
22
+ */
23
+ readonly driver?: () => ConnectionDriver;
24
+ /** Signs without a wallet connection (GPG). Such a family has no driver. */
25
+ readonly connectionless?: boolean;
26
+ /** Modal screens the family contributes. */
27
+ readonly screens?: readonly ScreenDef[];
28
+ /** A provider the family needs mounted around the app. */
29
+ readonly Provider?: ComponentType<{ children?: ReactNode }>;
30
+ }
31
+
32
+ export interface WalletFamily extends WalletFamilyDefinition {
33
+ readonly aliases: readonly string[];
34
+ readonly connectionless: boolean;
35
+ }
36
+
37
+ /** Validates a family definition and freezes it. */
38
+ export function defineWalletFamily(definition: WalletFamilyDefinition): WalletFamily {
39
+ assertName("wallet family id", definition.id);
40
+ const aliases = [...(definition.aliases ?? [])];
41
+ for (const alias of aliases) {
42
+ assertName(`wallet family ${definition.id} alias`, alias);
43
+ if (alias === definition.id) {
44
+ throw new Error(`wallet family ${definition.id} lists its own id as an alias`);
45
+ }
46
+ }
47
+ if (typeof definition.label !== "string" || !definition.label.trim()) {
48
+ throw new Error(`wallet family ${definition.id} needs a label`);
49
+ }
50
+ const { message } = definition;
51
+ if (!message || typeof message.header !== "string" || !message.header) {
52
+ throw new Error(`wallet family ${definition.id} needs a message spec with a header`);
53
+ }
54
+ const connectionless = definition.connectionless === true;
55
+ if (connectionless && definition.driver) {
56
+ throw new Error(`wallet family ${definition.id} is connectionless and cannot have a driver`);
57
+ }
58
+ if (!connectionless && typeof definition.driver !== "function") {
59
+ throw new Error(`wallet family ${definition.id} needs a driver, or connectionless: true`);
60
+ }
61
+ return Object.freeze({
62
+ ...definition,
63
+ aliases: Object.freeze(aliases),
64
+ connectionless,
65
+ message: Object.freeze({ ...message }),
66
+ ...(definition.screens ? { screens: Object.freeze([...definition.screens]) } : {}),
67
+ });
68
+ }
69
+
70
+ export interface FamilyRegistry {
71
+ /** In the order the app listed them, which is the connect list's order. */
72
+ readonly families: readonly WalletFamily[];
73
+ /** The family for an id or alias, or undefined. */
74
+ get(family: string): WalletFamily | undefined;
75
+ /** The family for an id or alias. Throws naming the registered families. */
76
+ require(family: string): WalletFamily;
77
+ readonly messages: WalletMessageRegistry;
78
+ }
79
+
80
+ /** The families one app registered. Refuses a family, id or alias registered twice. */
81
+ export function createFamilyRegistry(families: readonly WalletFamily[]): FamilyRegistry {
82
+ const list = Object.freeze([...families]);
83
+ const names = nameTable(list);
84
+ const byId = new Map(list.map((family) => [family.id, family]));
85
+ const get = (family: string) => {
86
+ const id = names.get(family);
87
+ return id === undefined ? undefined : byId.get(id);
88
+ };
89
+ return {
90
+ families: list,
91
+ get,
92
+ require(family) {
93
+ const found = get(family);
94
+ if (found === undefined) {
95
+ throw new Error(
96
+ `wallet family ${family} is not registered; registered: ${list.map((entry) => entry.id).join(", ") || "none"}`,
97
+ );
98
+ }
99
+ return found;
100
+ },
101
+ messages: createMessageRegistry(list),
102
+ };
103
+ }
@@ -0,0 +1,82 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join, relative } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import ts from "typescript";
5
+ import { describe, expect, it } from "vitest";
6
+
7
+ const src = fileURLToPath(new URL("..", import.meta.url));
8
+ const entry = fileURLToPath(new URL("./index.ts", import.meta.url));
9
+
10
+ function resolveRelative(from: string, specifier: string): string {
11
+ const base = join(dirname(from), specifier);
12
+ for (const candidate of [
13
+ `${base}.ts`,
14
+ `${base}.tsx`,
15
+ join(base, "index.ts"),
16
+ join(base, "index.tsx"),
17
+ ]) {
18
+ if (existsSync(candidate)) return candidate;
19
+ }
20
+ throw new Error(`cannot resolve ${specifier} from ${from}`);
21
+ }
22
+
23
+ /** Specifiers a file loads at runtime: type-only imports and exports are erased. */
24
+ function runtimeSpecifiers(file: string): string[] {
25
+ const source = ts.createSourceFile(
26
+ file,
27
+ readFileSync(file, "utf8"),
28
+ ts.ScriptTarget.Latest,
29
+ true,
30
+ );
31
+ const found: string[] = [];
32
+ const visit = (node: ts.Node) => {
33
+ if (ts.isImportDeclaration(node) && !node.importClause?.isTypeOnly) {
34
+ found.push((node.moduleSpecifier as ts.StringLiteral).text);
35
+ } else if (ts.isExportDeclaration(node) && node.moduleSpecifier && !node.isTypeOnly) {
36
+ found.push((node.moduleSpecifier as ts.StringLiteral).text);
37
+ } else if (
38
+ ts.isCallExpression(node) &&
39
+ (node.expression.kind === ts.SyntaxKind.ImportKeyword ||
40
+ (ts.isIdentifier(node.expression) && node.expression.text === "require")) &&
41
+ node.arguments[0] &&
42
+ ts.isStringLiteral(node.arguments[0])
43
+ ) {
44
+ found.push(node.arguments[0].text);
45
+ }
46
+ ts.forEachChild(node, visit);
47
+ };
48
+ visit(source);
49
+ return found;
50
+ }
51
+
52
+ function runtimeGraph(start: string) {
53
+ const files = new Set<string>();
54
+ const packages = new Map<string, string>();
55
+ const walk = (file: string) => {
56
+ if (files.has(file)) return;
57
+ files.add(file);
58
+ for (const specifier of runtimeSpecifiers(file)) {
59
+ if (specifier.startsWith(".")) walk(resolveRelative(file, specifier));
60
+ else packages.set(specifier, relative(src, file));
61
+ }
62
+ };
63
+ walk(start);
64
+ return { files: [...files].map((file) => relative(src, file)).sort(), packages };
65
+ }
66
+
67
+ describe("the core entry", () => {
68
+ it("loads no chain SDK: only React and its own files", () => {
69
+ const { files, packages } = runtimeGraph(entry);
70
+ expect(Object.fromEntries(packages)).toEqual({ react: "core/react.ts" });
71
+ expect(files.every((file) => file.startsWith("core/") || file === "clients/types.ts")).toBe(
72
+ true,
73
+ );
74
+ });
75
+
76
+ it("the scan sees a runtime import it should refuse", () => {
77
+ const { packages } = runtimeGraph(
78
+ fileURLToPath(new URL("../message/index.ts", import.meta.url)),
79
+ );
80
+ expect(packages.has("viem")).toBe(true);
81
+ });
82
+ });