git.iko.soy/rowtype-yoga/purescript-yoga-react-native
on main help sign in
PureScript Signal backend with provisioning QR code flow via libsignal-client
commit59a4c94acc5299cea8f17330d9aff59818029261 authorMark <mark.eibes@gmail.com> dateThu 19 Feb 2026, 20:20 (7 months ago) parent0b21c9a
17 files changed+2375
signal-backend/.gitignore+34 signal-backend/bun.lock+36 signal-backend/package.json+15 signal-backend/run.js+2 signal-backend/spago.lock+1769 signal-backend/spago.yaml+51 signal-backend/src/Main.js+26 signal-backend/src/Main.purs+53 signal-backend/src/Signal/Keys.js+15 signal-backend/src/Signal/Keys.purs+32 signal-backend/src/Signal/Net.js+15 signal-backend/src/Signal/Net.purs+56 signal-backend/src/Signal/Provisioning.purs+45 signal-backend/src/Signal/Provisioning/Cipher.js+144 signal-backend/src/Signal/Provisioning/Cipher.purs+60 signal-backend/src/Signal/Provisioning/QRCode.js+9 signal-backend/src/Signal/Provisioning/QRCode.purs+13
signal-backend/.gitignorenew file+34
@@ -0,0 +1,34 @@
1+# dependencies (bun install)
2+node_modules
3+
4+# output
5+out
6+dist
7+*.tgz
8+
9+# code coverage
10+coverage
11+*.lcov
12+
13+# logs
14+logs
15+_.log
16+report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
17+
18+# dotenv environment variable files
19+.env
20+.env.development.local
21+.env.test.local
22+.env.production.local
23+.env.local
24+
25+# caches
26+.eslintcache
27+.cache
28+*.tsbuildinfo
29+
30+# IntelliJ based IDEs
31+.idea
32+
33+# Finder (MacOS) folder config
34+.DS_Store
signal-backend/bun.locknew file+36
@@ -0,0 +1,36 @@
1+{
2+ "lockfileVersion": 1,
3+ "workspaces": {
4+ "": {
5+ "name": "signal-backend",
6+ "dependencies": {
7+ "@signalapp/libsignal-client": "^0.87.2",
8+ },
9+ "devDependencies": {
10+ "@types/bun": "latest",
11+ },
12+ "peerDependencies": {
13+ "typescript": "^5",
14+ },
15+ },
16+ },
17+ "packages": {
18+ "@signalapp/libsignal-client": ["@signalapp/libsignal-client@0.87.2", "", { "dependencies": { "node-gyp-build": "^4.8.0", "type-fest": "^4.26.0", "uuid": "^11" } }, "sha512-cKDJMEmG5cHaNfhEZkdaAFvh8AJ4XHb/lyENjPPKKZ4LrMThCoX0TdKuZDgHZbaeHr3NnORrCNavLI0QQMVvgA=="],
19+
20+ "@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
21+
22+ "@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="],
23+
24+ "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
25+
26+ "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="],
27+
28+ "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
29+
30+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
31+
32+ "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
33+
34+ "uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
35+ }
36+}
signal-backend/package.jsonnew file+15
@@ -0,0 +1,15 @@
1+{
2+ "name": "signal-backend",
3+ "module": "index.ts",
4+ "type": "module",
5+ "private": true,
6+ "devDependencies": {
7+ "@types/bun": "latest"
8+ },
9+ "peerDependencies": {
10+ "typescript": "^5"
11+ },
12+ "dependencies": {
13+ "@signalapp/libsignal-client": "^0.87.2"
14+ }
15+}
signal-backend/run.jsnew file+2
@@ -0,0 +1,2 @@
1+import { main } from "./output/Main/index.js";
2+main();
signal-backend/spago.locknew file+1769
@@ -0,0 +1,1769 @@
1+{
2+ "workspace": {
3+ "packages": {
4+ "signal-backend": {
5+ "path": "./",
6+ "core": {
7+ "dependencies": [
8+ "aff",
9+ "arraybuffer-types",
10+ "arrays",
11+ "avar",
12+ "console",
13+ "effect",
14+ "either",
15+ "foldable-traversable",
16+ "foreign",
17+ "foreign-object",
18+ "js-promise",
19+ "js-promise-aff",
20+ "maybe",
21+ "newtype",
22+ "nullable",
23+ "prelude",
24+ "refs",
25+ "strings",
26+ "tuples",
27+ "unsafe-coerce",
28+ "variant",
29+ "web-fetch",
30+ "yoga-bun-yoga",
31+ "yoga-json",
32+ "yoga-om",
33+ "yoga-om-layer",
34+ "yoga-sqlite",
35+ "yoga-sqlite-om"
36+ ]
37+ },
38+ "test": {
39+ "dependencies": []
40+ }
41+ }
42+ },
43+ "package_set": {
44+ "address": {
45+ "registry": "73.0.0"
46+ },
47+ "compiler": ">=0.15.15 <0.16.0",
48+ "content": {
49+ "abc-parser": "2.1.0",
50+ "ace": "9.1.0",
51+ "address-rfc2821": "0.1.1",
52+ "aff": "8.0.0",
53+ "aff-bus": "6.0.0",
54+ "aff-coroutines": "9.0.0",
55+ "aff-promise": "4.0.0",
56+ "aff-retry": "2.0.0",
57+ "affjax": "13.0.0",
58+ "affjax-node": "1.0.0",
59+ "affjax-web": "1.0.0",
60+ "ansi": "7.0.0",
61+ "apexcharts": "0.5.0",
62+ "applicative-phases": "1.0.0",
63+ "argonaut": "9.0.0",
64+ "argonaut-aeson-generic": "0.4.1",
65+ "argonaut-codecs": "9.1.0",
66+ "argonaut-core": "7.0.0",
67+ "argonaut-generic": "8.0.0",
68+ "argonaut-traversals": "10.0.0",
69+ "argparse-basic": "2.0.0",
70+ "array-builder": "0.1.2",
71+ "array-search": "0.6.0",
72+ "arraybuffer": "13.2.0",
73+ "arraybuffer-builder": "3.1.0",
74+ "arraybuffer-types": "3.0.2",
75+ "arrays": "7.3.0",
76+ "arrays-extra": "0.6.1",
77+ "arrays-zipper": "2.0.1",
78+ "ask": "1.0.0",
79+ "assert": "6.0.0",
80+ "assert-multiple": "0.4.0",
81+ "avar": "5.0.1",
82+ "axon": "0.0.3",
83+ "b64": "0.0.8",
84+ "barbies": "1.0.1",
85+ "barlow-lens": "0.9.0",
86+ "benchlib": "0.0.4",
87+ "bifunctors": "6.1.0",
88+ "bigints": "7.0.1",
89+ "blessed": "1.0.0",
90+ "bolson": "0.3.9",
91+ "bookhound": "0.1.7",
92+ "bower-json": "3.0.0",
93+ "call-by-name": "4.0.1",
94+ "canvas": "6.0.0",
95+ "canvas-action": "9.0.0",
96+ "cartesian": "1.0.6",
97+ "catenable-lists": "7.0.0",
98+ "cbor-stream": "1.3.0",
99+ "chameleon": "1.0.0",
100+ "chameleon-halogen": "1.0.3",
101+ "chameleon-react-basic": "1.1.0",
102+ "chameleon-styled": "2.5.0",
103+ "chameleon-transformers": "1.0.0",
104+ "channel": "1.0.0",
105+ "checked-exceptions": "3.1.1",
106+ "choku": "1.0.2",
107+ "classless": "0.1.1",
108+ "classless-arbitrary": "0.1.1",
109+ "classless-decode-json": "0.1.1",
110+ "classless-encode-json": "0.1.3",
111+ "classnames": "2.0.0",
112+ "codec": "6.1.0",
113+ "codec-argonaut": "10.0.0",
114+ "codec-json": "2.0.0",
115+ "colors": "7.0.1",
116+ "concur-core": "0.5.0",
117+ "concur-react": "0.5.0",
118+ "concurrent-queues": "3.0.0",
119+ "console": "6.1.0",
120+ "const": "6.0.0",
121+ "contravariant": "6.0.0",
122+ "control": "6.0.0",
123+ "convertable-options": "1.0.0",
124+ "coroutines": "7.0.0",
125+ "css": "6.0.0",
126+ "css-class-name-extractor": "0.0.4",
127+ "css-frameworks": "1.1.0",
128+ "csv-stream": "2.3.0",
129+ "data-mvc": "0.0.2",
130+ "datetime": "6.1.0",
131+ "datetime-parsing": "0.2.0",
132+ "debounce": "0.1.0",
133+ "debug": "6.0.2",
134+ "decimals": "7.1.0",
135+ "default-values": "1.0.1",
136+ "deku": "0.9.23",
137+ "deno": "0.0.5",
138+ "dissect": "1.0.0",
139+ "distributive": "6.0.0",
140+ "dodo-printer": "2.2.3",
141+ "dom-filereader": "7.0.0",
142+ "dom-indexed": "13.0.0",
143+ "dom-simple": "0.4.0",
144+ "dotenv": "4.0.3",
145+ "droplet": "0.6.0",
146+ "dts": "1.0.0",
147+ "dual-numbers": "1.0.3",
148+ "dynamic-buffer": "3.0.1",
149+ "echarts-simple": "0.0.1",
150+ "effect": "4.0.0",
151+ "either": "6.1.0",
152+ "elmish": "0.13.0",
153+ "elmish-enzyme": "0.1.1",
154+ "elmish-hooks": "0.11.0",
155+ "elmish-html": "0.10.0",
156+ "elmish-testing-library": "0.3.2",
157+ "elmish-time-machine": "0.4.2",
158+ "email-validate": "7.0.0",
159+ "encoding": "0.0.9",
160+ "enums": "6.0.1",
161+ "env-names": "0.4.0",
162+ "environment": "2.0.0",
163+ "error": "2.0.0",
164+ "eta-conversion": "0.3.2",
165+ "exceptions": "6.1.0",
166+ "exists": "6.0.0",
167+ "exitcodes": "4.0.0",
168+ "expect-inferred": "3.0.0",
169+ "express": "0.9.1",
170+ "ezfetch": "1.1.1",
171+ "fahrtwind": "2.0.0",
172+ "fakerjs": "0.0.1",
173+ "fallback": "0.1.0",
174+ "fast-vect": "1.2.0",
175+ "fetch": "4.1.0",
176+ "fetch-argonaut": "1.0.1",
177+ "fetch-core": "5.1.0",
178+ "fetch-yoga-json": "1.1.0",
179+ "ffi-simple": "0.5.1",
180+ "fft": "0.3.0",
181+ "fft-js": "0.1.0",
182+ "filterable": "5.0.0",
183+ "fix-functor": "0.1.0",
184+ "fixed-points": "7.0.0",
185+ "fixed-precision": "5.0.0",
186+ "flame": "1.6.0",
187+ "float32": "2.0.0",
188+ "fmt": "0.2.1",
189+ "foldable-traversable": "6.0.0",
190+ "foldable-traversable-extra": "0.0.6",
191+ "foreign": "7.0.0",
192+ "foreign-object": "4.1.0",
193+ "foreign-readwrite": "3.4.0",
194+ "forgetmenot": "0.1.0",
195+ "fork": "6.0.0",
196+ "form-urlencoded": "7.0.0",
197+ "formatters": "7.0.0",
198+ "framer-motion": "2.0.1",
199+ "free": "7.1.0",
200+ "freeap": "7.0.0",
201+ "freer-free": "0.0.1",
202+ "freet": "7.0.0",
203+ "functions": "6.0.0",
204+ "functor1": "3.0.0",
205+ "functors": "5.0.0",
206+ "fuzzy": "0.4.0",
207+ "gen": "4.0.0",
208+ "generate-values": "1.0.1",
209+ "generic-router": "0.0.1",
210+ "geojson": "0.0.5",
211+ "geometria": "2.2.0",
212+ "gesso": "1.0.1",
213+ "gojs": "0.1.1",
214+ "golem-fetch": "0.1.0",
215+ "grain": "3.0.0",
216+ "grain-router": "3.0.0",
217+ "grain-virtualized": "3.0.0",
218+ "graphql-client": "10.1.1",
219+ "graphs": "8.1.0",
220+ "group": "4.1.1",
221+ "halogen": "7.0.0",
222+ "halogen-bootstrap5": "5.3.2",
223+ "halogen-canvas": "1.0.0",
224+ "halogen-css": "10.0.0",
225+ "halogen-declarative-canvas": "0.0.8",
226+ "halogen-echarts-simple": "0.0.4",
227+ "halogen-formless": "4.0.3",
228+ "halogen-helix": "1.1.0",
229+ "halogen-hooks": "0.6.3",
230+ "halogen-hooks-extra": "0.9.0",
231+ "halogen-infinite-scroll": "1.1.0",
232+ "halogen-store": "0.5.4",
233+ "halogen-storybook": "2.0.0",
234+ "halogen-subscriptions": "2.0.0",
235+ "halogen-svg-elems": "8.0.0",
236+ "halogen-typewriter": "1.0.4",
237+ "halogen-use-trigger-hooks": "1.0.0",
238+ "halogen-vdom": "8.0.0",
239+ "halogen-vdom-string-renderer": "0.5.0",
240+ "halogen-xterm": "2.0.0",
241+ "heckin": "2.0.1",
242+ "heterogeneous": "0.7.0",
243+ "homogeneous": "0.4.0",
244+ "http-methods": "6.0.0",
245+ "httpurple": "4.0.0",
246+ "huffman": "0.4.0",
247+ "humdrum": "0.0.1",
248+ "hylograph-canvas": "0.1.0",
249+ "hylograph-d3-kernel": "0.1.0",
250+ "hylograph-graph": "0.1.0",
251+ "hylograph-layout": "0.1.0",
252+ "hylograph-music": "0.1.0",
253+ "hylograph-optics": "0.1.0",
254+ "hylograph-selection": "0.1.1",
255+ "hylograph-simulation": "0.1.0",
256+ "hylograph-simulation-core": "0.1.0",
257+ "hylograph-simulation-halogen": "0.1.0",
258+ "hylograph-transitions": "0.1.0",
259+ "hylograph-wasm-kernel": "0.1.0",
260+ "hyrule": "2.3.8",
261+ "identity": "6.0.0",
262+ "identy": "4.0.1",
263+ "indexed-db": "1.0.0",
264+ "indexed-monad": "3.0.0",
265+ "ink": "0.0.1",
266+ "int64": "3.0.0",
267+ "integers": "6.0.0",
268+ "interpolate": "5.0.2",
269+ "intersection-observer": "1.0.1",
270+ "invariant": "6.0.0",
271+ "jarilo": "1.0.1",
272+ "jelly": "0.10.0",
273+ "jelly-router": "0.3.0",
274+ "jelly-signal": "0.4.0",
275+ "jest": "1.0.0",
276+ "js-abort-controller": "1.0.0",
277+ "js-bigints": "2.2.1",
278+ "js-date": "8.0.0",
279+ "js-fetch": "0.2.1",
280+ "js-fileio": "3.0.0",
281+ "js-intl": "1.1.4",
282+ "js-iterators": "0.1.1",
283+ "js-maps": "0.1.2",
284+ "js-promise": "1.0.0",
285+ "js-promise-aff": "1.0.0",
286+ "js-timers": "6.1.0",
287+ "js-uri": "3.1.0",
288+ "jsdom": "1.0.0",
289+ "json": "1.1.0",
290+ "json-codecs": "5.0.0",
291+ "justifill": "0.5.0",
292+ "jwt": "0.0.9",
293+ "labeled-data": "0.2.0",
294+ "language-cst-parser": "0.14.1",
295+ "lazy": "6.0.0",
296+ "lazy-joe": "1.0.0",
297+ "lcg": "4.0.0",
298+ "leibniz": "5.0.0",
299+ "leveldb": "1.0.1",
300+ "liminal": "1.0.1",
301+ "linalg": "6.0.0",
302+ "lists": "7.0.0",
303+ "literals": "1.0.2",
304+ "logging": "3.0.0",
305+ "logging-journald": "0.4.0",
306+ "lumi-components": "18.0.0",
307+ "machines": "7.0.0",
308+ "maps-eager": "0.5.0",
309+ "marionette": "1.0.0",
310+ "marionette-react-basic-hooks": "0.1.1",
311+ "marked": "0.1.0",
312+ "matrices": "5.0.1",
313+ "matryoshka": "1.0.0",
314+ "maybe": "6.0.0",
315+ "media-types": "6.0.0",
316+ "meowclient": "1.0.0",
317+ "midi": "4.0.0",
318+ "milkis": "9.0.0",
319+ "mimetype": "0.0.1",
320+ "minibench": "4.0.1",
321+ "mmorph": "7.0.0",
322+ "monad-control": "5.0.0",
323+ "monad-logger": "1.3.1",
324+ "monad-loops": "0.5.0",
325+ "monad-unlift": "1.0.1",
326+ "monoid-extras": "0.0.1",
327+ "monoidal": "0.16.0",
328+ "morello": "0.4.0",
329+ "mote": "3.0.0",
330+ "motsunabe": "2.0.0",
331+ "mvc": "0.0.1",
332+ "mysql": "6.0.1",
333+ "n3": "0.1.0",
334+ "nano-id": "1.1.0",
335+ "nanoid": "0.1.0",
336+ "naturals": "3.0.0",
337+ "nested-functor": "0.2.1",
338+ "newtype": "5.0.0",
339+ "nextjs": "0.1.1",
340+ "nextui": "0.2.0",
341+ "node-buffer": "9.0.0",
342+ "node-child-process": "11.1.0",
343+ "node-event-emitter": "3.0.0",
344+ "node-execa": "5.0.0",
345+ "node-fs": "9.2.0",
346+ "node-glob-basic": "2.0.0",
347+ "node-http": "9.1.0",
348+ "node-http2": "1.1.1",
349+ "node-human-signals": "1.0.0",
350+ "node-net": "5.1.0",
351+ "node-os": "5.1.0",
352+ "node-path": "5.0.1",
353+ "node-process": "11.2.0",
354+ "node-readline": "8.1.1",
355+ "node-sqlite3": "8.0.0",
356+ "node-stream-pipes": "2.1.6",
357+ "node-streams": "9.0.1",
358+ "node-tls": "0.3.1",
359+ "node-url": "7.0.1",
360+ "node-workerbees": "0.3.1",
361+ "node-zlib": "0.4.0",
362+ "nonempty": "7.0.0",
363+ "now": "6.0.0",
364+ "npm-package-json": "2.0.0",
365+ "nullable": "6.0.0",
366+ "numberfield": "0.2.2",
367+ "numbers": "9.0.1",
368+ "oak": "3.1.1",
369+ "oak-debug": "1.2.2",
370+ "object-maps": "0.3.0",
371+ "ocarina": "1.5.4",
372+ "oooooooooorrrrrrrmm-lib": "0.0.1",
373+ "open-colors-scales-and-schemes": "1.0.0",
374+ "open-drawing": "6.0.4",
375+ "open-folds": "6.4.0",
376+ "open-foreign-generic": "11.0.3",
377+ "open-memoize": "6.2.0",
378+ "open-mkdirp-aff": "1.2.0",
379+ "open-pairing": "6.2.0",
380+ "open-smolder": "12.0.2",
381+ "options": "7.0.0",
382+ "optparse": "5.0.1",
383+ "ordered-collections": "3.2.0",
384+ "ordered-set": "0.4.0",
385+ "orders": "6.0.0",
386+ "org-doc": "0.1.0",
387+ "owoify": "1.2.0",
388+ "pairs": "9.0.1",
389+ "parallel": "7.0.0",
390+ "parsing": "11.0.0",
391+ "parsing-dataview": "3.2.4",
392+ "partial": "4.0.0",
393+ "pathy": "9.0.0",
394+ "pha": "0.13.0",
395+ "phaser": "0.7.0",
396+ "phylio": "1.1.2",
397+ "pipes": "8.0.0",
398+ "pirates-charm": "0.0.1",
399+ "play": "1.0.0",
400+ "pmock": "0.9.0",
401+ "point-free": "1.0.0",
402+ "pointed-list": "0.5.1",
403+ "polymorphic-vectors": "4.0.0",
404+ "posix-types": "6.0.0",
405+ "postgresql": "2.0.21",
406+ "precise": "6.0.0",
407+ "precise-datetime": "7.0.0",
408+ "prelude": "6.0.2",
409+ "prettier-printer": "3.0.0",
410+ "printf": "0.1.0",
411+ "priority-queue": "0.1.2",
412+ "profunctor": "6.0.1",
413+ "profunctor-lenses": "8.0.0",
414+ "protobuf": "4.4.0",
415+ "psa-utils": "8.0.0",
416+ "psci-support": "6.0.0",
417+ "punycode": "1.0.0",
418+ "pursfmt": "0.17.0",
419+ "qualified-do": "2.2.0",
420+ "quantities": "12.2.0",
421+ "quickcheck": "8.0.1",
422+ "quickcheck-combinators": "0.1.3",
423+ "quickcheck-laws": "7.0.0",
424+ "quickcheck-utf8": "0.0.0",
425+ "random": "6.0.0",
426+ "rationals": "6.0.0",
427+ "rdf": "0.1.0",
428+ "react": "11.0.0",
429+ "react-aria": "0.2.0",
430+ "react-basic": "17.0.0",
431+ "react-basic-classic": "3.0.0",
432+ "react-basic-dnd": "10.1.0",
433+ "react-basic-dom": "7.0.0",
434+ "react-basic-dom-beta": "0.1.1",
435+ "react-basic-emotion": "7.1.0",
436+ "react-basic-hooks": "9.0.0",
437+ "react-basic-storybook": "2.0.0",
438+ "react-dom": "8.0.0",
439+ "react-halo": "3.0.0",
440+ "react-icons": "1.1.5",
441+ "react-markdown": "0.1.0",
442+ "react-testing-library": "4.0.1",
443+ "react-virtuoso": "1.0.0",
444+ "reactix": "0.6.1",
445+ "read": "1.0.1",
446+ "recharts": "1.1.0",
447+ "record": "4.0.0",
448+ "record-extra": "5.0.1",
449+ "record-extra-srghma": "0.2.8",
450+ "record-ptional-fields": "0.1.2",
451+ "record-studio": "1.0.4",
452+ "refs": "6.0.0",
453+ "remotedata": "5.0.1",
454+ "repr": "0.5.0",
455+ "resize-arrays": "0.0.1",
456+ "resize-observer": "1.0.0",
457+ "resource": "2.0.1",
458+ "resourcet": "1.0.0",
459+ "result": "1.0.3",
460+ "return": "0.2.0",
461+ "ring-modules": "5.0.1",
462+ "rito": "0.3.4",
463+ "roman": "0.4.0",
464+ "rough-notation": "1.0.2",
465+ "routing": "11.0.0",
466+ "routing-duplex": "0.7.0",
467+ "run": "5.0.0",
468+ "safe-coerce": "2.0.0",
469+ "safely": "4.0.1",
470+ "school-of-music": "1.3.0",
471+ "selection-foldable": "0.2.0",
472+ "selective-functors": "1.0.1",
473+ "semirings": "7.0.0",
474+ "shuffle": "2.0.0",
475+ "signal": "13.0.0",
476+ "simple-emitter": "3.0.1",
477+ "simple-i18n": "2.0.1",
478+ "simple-json": "9.0.0",
479+ "simple-json-generics": "0.2.1",
480+ "simple-ulid": "3.0.0",
481+ "sized-matrices": "1.0.0",
482+ "sized-vectors": "5.0.2",
483+ "slug": "3.2.0",
484+ "small-ffi": "4.0.1",
485+ "soundfonts": "4.1.0",
486+ "sparse-matrices": "2.0.1",
487+ "sparse-polynomials": "3.0.1",
488+ "spec": "8.1.1",
489+ "spec-discovery": "8.4.0",
490+ "spec-mocha": "5.1.1",
491+ "spec-node": "0.0.3",
492+ "spec-quickcheck": "5.0.2",
493+ "spec-reporter-xunit": "0.7.1",
494+ "splitmix": "2.1.0",
495+ "ssrs": "1.0.0",
496+ "st": "6.2.0",
497+ "statistics": "0.3.2",
498+ "strictlypositiveint": "1.0.1",
499+ "string-parsers": "8.0.0",
500+ "strings": "6.0.1",
501+ "strings-extra": "4.0.0",
502+ "stringutils": "0.0.12",
503+ "structured-logging": "1.0.0",
504+ "substitute": "0.2.3",
505+ "supply": "0.2.0",
506+ "svg-parser": "3.0.0",
507+ "systemd-journald": "0.3.0",
508+ "tagged": "4.0.2",
509+ "tailrec": "6.1.0",
510+ "tanstack-query": "2.0.0",
511+ "tecton": "0.2.1",
512+ "tecton-halogen": "0.2.0",
513+ "test-unit": "17.0.0",
514+ "text-formatting": "0.1.0",
515+ "thermite": "6.3.1",
516+ "thermite-dom": "0.3.1",
517+ "these": "6.0.0",
518+ "threading": "0.0.3",
519+ "tidy": "0.11.1",
520+ "tidy-codegen": "4.0.1",
521+ "tldr": "0.0.0",
522+ "toestand": "0.9.0",
523+ "transformation-matrix": "1.0.1",
524+ "transformers": "6.1.0",
525+ "transit": "1.0.0",
526+ "tree-rose": "4.0.2",
527+ "trivial-unfold": "0.5.0",
528+ "ts-bridge": "4.0.0",
529+ "tuples": "7.0.0",
530+ "two-or-more": "1.0.0",
531+ "type-equality": "4.0.1",
532+ "typedenv": "2.0.1",
533+ "typelevel": "6.0.0",
534+ "typelevel-lists": "2.1.0",
535+ "typelevel-peano": "1.0.1",
536+ "typelevel-prelude": "7.0.0",
537+ "typelevel-regex": "0.0.3",
538+ "typelevel-rows": "0.1.0",
539+ "typisch": "0.4.0",
540+ "uint": "7.0.0",
541+ "ulid": "3.0.1",
542+ "uncurried-transformers": "1.1.0",
543+ "undefined": "2.0.0",
544+ "undefined-is-not-a-problem": "1.1.0",
545+ "unfoldable": "6.0.0",
546+ "unicode": "6.0.0",
547+ "unique": "0.6.1",
548+ "unlift": "1.0.1",
549+ "unordered-collections": "3.1.0",
550+ "unsafe-coerce": "6.0.0",
551+ "unsafe-reference": "5.0.0",
552+ "untagged-to-tagged": "0.1.4",
553+ "untagged-union": "1.0.0",
554+ "uri": "9.0.0",
555+ "url-immutable": "1.0.0",
556+ "url-regex-safe": "0.1.1",
557+ "uuid": "9.0.0",
558+ "uuidv4": "1.0.0",
559+ "validation": "6.0.0",
560+ "variant": "8.0.0",
561+ "variant-encodings": "2.0.0",
562+ "variant-gen": "1.0.0",
563+ "vectorfield": "1.0.1",
564+ "vectors": "2.1.0",
565+ "versions": "7.0.0",
566+ "visx": "0.0.2",
567+ "vitest": "2.0.0",
568+ "web-clipboard": "6.0.0",
569+ "web-cssom": "2.0.0",
570+ "web-cssom-view": "0.1.0",
571+ "web-dom": "6.0.0",
572+ "web-dom-parser": "8.0.0",
573+ "web-dom-xpath": "3.0.0",
574+ "web-encoding": "3.0.0",
575+ "web-events": "4.0.0",
576+ "web-fetch": "4.0.1",
577+ "web-file": "4.0.0",
578+ "web-geometry": "0.1.0",
579+ "web-html": "4.1.1",
580+ "web-pointerevents": "2.0.0",
581+ "web-proletarian": "1.0.0",
582+ "web-promise": "3.2.0",
583+ "web-resize-observer": "2.1.0",
584+ "web-router": "1.0.0",
585+ "web-socket": "4.0.0",
586+ "web-storage": "5.0.0",
587+ "web-streams": "4.0.0",
588+ "web-touchevents": "4.0.0",
589+ "web-uievents": "5.0.0",
590+ "web-url": "2.0.0",
591+ "web-workers": "1.1.0",
592+ "web-xhr": "5.0.1",
593+ "webb-aff-list": "0.0.2",
594+ "webb-array": "0.0.2",
595+ "webb-channel": "0.0.3",
596+ "webb-commandline": "0.0.3",
597+ "webb-directory": "0.0.7",
598+ "webb-file": "0.0.1",
599+ "webb-map": "0.0.1",
600+ "webb-monad": "0.0.2",
601+ "webb-mutex": "0.0.7",
602+ "webb-parsing": "0.0.5",
603+ "webb-random": "0.0.1",
604+ "webb-refer": "0.0.3",
605+ "webb-set": "0.0.1",
606+ "webb-slot": "0.0.1",
607+ "webb-stateful": "0.0.3",
608+ "webb-string": "0.0.1",
609+ "webb-test": "0.0.1",
610+ "webb-thread": "0.0.2",
611+ "webb-writer": "0.0.3",
612+ "webextension-polyfill": "0.1.0",
613+ "webgpu": "0.0.1",
614+ "which": "2.0.0",
615+ "whine-core": "0.0.33",
616+ "xterm": "1.0.0",
617+ "yaml-next": "3.1.1",
618+ "yoga-fetch": "1.0.1",
619+ "yoga-json": "5.1.0",
620+ "yoga-om": "0.1.0",
621+ "yoga-postgres": "6.0.0",
622+ "yoga-react-dom": "1.0.1",
623+ "yoga-redis": "0.1.0",
624+ "yoga-sql-types": "0.1.0",
625+ "yoga-subtlecrypto": "0.1.0",
626+ "yoga-test-docker": "0.1.0",
627+ "yoga-tree": "1.0.0",
628+ "yoga-tree-svg": "0.1.0",
629+ "yoga-tree-utils": "1.0.0",
630+ "z3": "0.0.2",
631+ "zipperarray": "2.0.0"
632+ }
633+ },
634+ "extra_packages": {
635+ "ref-lifted": {
636+ "path": "/Users/mark/code/purescript-ref-lifted"
637+ },
638+ "yoga-bun-yoga": {
639+ "path": "/Users/mark/code/purescript-yoga-bun-yoga"
640+ },
641+ "yoga-json": "5.2.0",
642+ "yoga-om": {
643+ "path": "/Users/mark/code/purescript-yoga-om"
644+ },
645+ "yoga-om-layer": {
646+ "path": "/Users/mark/code/purescript-yoga-om-layer"
647+ },
648+ "yoga-sql-types": {
649+ "path": "/Users/mark/code/purescript-yoga-sql-types"
650+ },
651+ "yoga-sqlite": {
652+ "path": "/Users/mark/code/purescript-yoga-sqlite"
653+ },
654+ "yoga-sqlite-om": {
655+ "path": "/Users/mark/code/purescript-yoga-sqlite-om"
656+ }
657+ }
658+ },
659+ "packages": {
660+ "aff": {
661+ "type": "registry",
662+ "version": "8.0.0",
663+ "integrity": "sha256-9BRIrSdRoYybfiUmBqgKJ66cPqxYHb277bVWQMn/9sg=",
664+ "dependencies": [
665+ "control",
666+ "datetime",
667+ "effect",
668+ "either",
669+ "exceptions",
670+ "functions",
671+ "newtype",
672+ "parallel",
673+ "partial",
674+ "prelude",
675+ "st",
676+ "tailrec",
677+ "transformers",
678+ "unsafe-coerce"
679+ ]
680+ },
681+ "aff-retry": {
682+ "type": "registry",
683+ "version": "2.0.0",
684+ "integrity": "sha256-rKZNcP4IKSyBFQBVzrCvE0ULoJt0N/uDLLJ0dPotr4k=",
685+ "dependencies": [
686+ "aff",
687+ "arrays",
688+ "datetime",
689+ "effect",
690+ "either",
691+ "exceptions",
692+ "integers",
693+ "maybe",
694+ "newtype",
695+ "numbers",
696+ "prelude",
697+ "random",
698+ "transformers"
699+ ]
700+ },
701+ "arraybuffer-types": {
702+ "type": "registry",
703+ "version": "3.0.2",
704+ "integrity": "sha256-p05cJnSkyeoB7VHzMyc2Eb1RwUaB7Nl0sQSqEGNEUD8=",
705+ "dependencies": []
706+ },
707+ "arrays": {
708+ "type": "registry",
709+ "version": "7.3.0",
710+ "integrity": "sha256-GD4z7LCi9wzi7FCQqbWhvs8paoUK9NO+HwgXZ+KP8Rk=",
711+ "dependencies": [
712+ "bifunctors",
713+ "control",
714+ "foldable-traversable",
715+ "functions",
716+ "maybe",
717+ "nonempty",
718+ "partial",
719+ "prelude",
720+ "safe-coerce",
721+ "st",
722+ "tailrec",
723+ "tuples",
724+ "unfoldable",
725+ "unsafe-coerce"
726+ ]
727+ },
728+ "avar": {
729+ "type": "registry",
730+ "version": "5.0.1",
731+ "integrity": "sha256-8V4SxF4TIWZ9Ik1P4lPzIRUAZeFBe8w5WA2VcCEKsIk=",
732+ "dependencies": [
733+ "aff",
734+ "effect",
735+ "either",
736+ "exceptions",
737+ "functions",
738+ "maybe",
739+ "prelude"
740+ ]
741+ },
742+ "bifunctors": {
743+ "type": "registry",
744+ "version": "6.1.0",
745+ "integrity": "sha256-CvZRb8uI75eKIqiYntR+k05Wk6tKlMS5w8sCA2AFna0=",
746+ "dependencies": [
747+ "const",
748+ "either",
749+ "newtype",
750+ "prelude",
751+ "tuples"
752+ ]
753+ },
754+ "catenable-lists": {
755+ "type": "registry",
756+ "version": "7.0.0",
757+ "integrity": "sha256-7+i/MHBLIRh604iCNT4ENWRWE/wuHIotER4kpdX9Ve0=",
758+ "dependencies": [
759+ "control",
760+ "foldable-traversable",
761+ "lists",
762+ "maybe",
763+ "prelude",
764+ "tuples",
765+ "unfoldable"
766+ ]
767+ },
768+ "console": {
769+ "type": "registry",
770+ "version": "6.1.0",
771+ "integrity": "sha256-3T5PPz1mATZmV0C4GzkCkpLlCa5xG7u8ZNgnLrrrI1Q=",
772+ "dependencies": [
773+ "effect",
774+ "prelude"
775+ ]
776+ },
777+ "const": {
778+ "type": "registry",
779+ "version": "6.0.0",
780+ "integrity": "sha256-S+m0lQsxnji0z1XRUmkn+3RFuWJ5CXWuIr1u3i8Bfoc=",
781+ "dependencies": [
782+ "invariant",
783+ "newtype",
784+ "prelude"
785+ ]
786+ },
787+ "contravariant": {
788+ "type": "registry",
789+ "version": "6.0.0",
790+ "integrity": "sha256-nuckEWqL80NaPmR/l9AUyrfycNh18XcvlZV9PAamwqc=",
791+ "dependencies": [
792+ "const",
793+ "either",
794+ "newtype",
795+ "prelude",
796+ "tuples"
797+ ]
798+ },
799+ "control": {
800+ "type": "registry",
801+ "version": "6.0.0",
802+ "integrity": "sha256-0vxyYh5FL+ilxgLo954w1LqCUC0RSIivNNlb9CaNUQg=",
803+ "dependencies": [
804+ "newtype",
805+ "prelude"
806+ ]
807+ },
808+ "datetime": {
809+ "type": "registry",
810+ "version": "6.1.0",
811+ "integrity": "sha256-tbv6eDXy6QOD7YSknxYSbsDF32OC2JrNbSBpzMs7Doc=",
812+ "dependencies": [
813+ "bifunctors",
814+ "control",
815+ "either",
816+ "enums",
817+ "foldable-traversable",
818+ "functions",
819+ "gen",
820+ "integers",
821+ "lists",
822+ "maybe",
823+ "newtype",
824+ "numbers",
825+ "ordered-collections",
826+ "partial",
827+ "prelude",
828+ "tuples"
829+ ]
830+ },
831+ "distributive": {
832+ "type": "registry",
833+ "version": "6.0.0",
834+ "integrity": "sha256-netyJK4B32PDYHD0tF2AenKgo3urzbI770ycM0pArAo=",
835+ "dependencies": [
836+ "identity",
837+ "newtype",
838+ "prelude",
839+ "tuples",
840+ "type-equality"
841+ ]
842+ },
843+ "effect": {
844+ "type": "registry",
845+ "version": "4.0.0",
846+ "integrity": "sha256-DdqU3bncRTjgMVPPRlAuGHVeajSPEcRaRhNuio7if6s=",
847+ "dependencies": [
848+ "prelude"
849+ ]
850+ },
851+ "either": {
852+ "type": "registry",
853+ "version": "6.1.0",
854+ "integrity": "sha256-tBHx3PgtH4GZOApZCDkcM7szRTYJinrUrVGWazQCUUg=",
855+ "dependencies": [
856+ "control",
857+ "invariant",
858+ "maybe",
859+ "prelude"
860+ ]
861+ },
862+ "enums": {
863+ "type": "registry",
864+ "version": "6.0.1",
865+ "integrity": "sha256-sdZOmLX5+5pASGpvVyT0vSD5BBYQjZiayjV5XiPutso=",
866+ "dependencies": [
867+ "control",
868+ "either",
869+ "gen",
870+ "maybe",
871+ "newtype",
872+ "nonempty",
873+ "partial",
874+ "prelude",
875+ "tuples",
876+ "unfoldable"
877+ ]
878+ },
879+ "exceptions": {
880+ "type": "registry",
881+ "version": "6.1.0",
882+ "integrity": "sha256-nRrr+N0wk0TUFOWLR7e1n1oxcXo/X345bQUtoY9lW6A=",
883+ "dependencies": [
884+ "effect",
885+ "either",
886+ "maybe",
887+ "prelude"
888+ ]
889+ },
890+ "exists": {
891+ "type": "registry",
892+ "version": "6.0.0",
893+ "integrity": "sha256-vtLbrWNaI+pzx/2fw2AQnCovoLtcosClIhjO26QKkh8=",
894+ "dependencies": [
895+ "unsafe-coerce"
896+ ]
897+ },
898+ "foldable-traversable": {
899+ "type": "registry",
900+ "version": "6.0.0",
901+ "integrity": "sha256-NGGWyCio/xjce6fPP7y3wwg7CheqllID6aJudDxbWhA=",
902+ "dependencies": [
903+ "bifunctors",
904+ "const",
905+ "control",
906+ "either",
907+ "functors",
908+ "identity",
909+ "maybe",
910+ "newtype",
911+ "orders",
912+ "prelude",
913+ "tuples"
914+ ]
915+ },
916+ "foreign": {
917+ "type": "registry",
918+ "version": "7.0.0",
919+ "integrity": "sha256-jiDRMVQfovGsbbA/FzbzYf6iWk9i2b5gNrIjz+gFfsI=",
920+ "dependencies": [
921+ "either",
922+ "functions",
923+ "integers",
924+ "lists",
925+ "maybe",
926+ "prelude",
927+ "strings",
928+ "transformers",
929+ "unsafe-coerce"
930+ ]
931+ },
932+ "foreign-object": {
933+ "type": "registry",
934+ "version": "4.1.0",
935+ "integrity": "sha256-x/Q7r80z/vmHRKvPwhYmHP/DnJciPtsyGfRTMzayKIU=",
936+ "dependencies": [
937+ "arrays",
938+ "foldable-traversable",
939+ "functions",
940+ "gen",
941+ "lists",
942+ "maybe",
943+ "prelude",
944+ "st",
945+ "tailrec",
946+ "tuples",
947+ "typelevel-prelude",
948+ "unfoldable",
949+ "unsafe-coerce"
950+ ]
951+ },
952+ "free": {
953+ "type": "registry",
954+ "version": "7.1.0",
955+ "integrity": "sha256-tUBInfUpRQEw4u94GWcYW7EYdmSdDHSRn88a+C0eltU=",
956+ "dependencies": [
957+ "catenable-lists",
958+ "control",
959+ "distributive",
960+ "either",
961+ "exists",
962+ "foldable-traversable",
963+ "invariant",
964+ "lazy",
965+ "maybe",
966+ "prelude",
967+ "tailrec",
968+ "transformers",
969+ "tuples",
970+ "unsafe-coerce"
971+ ]
972+ },
973+ "functions": {
974+ "type": "registry",
975+ "version": "6.0.0",
976+ "integrity": "sha256-0jgtOr+SFO4UScKc40oA/9Vdcf2rLn3h3vZlY0KfqVo=",
977+ "dependencies": [
978+ "prelude"
979+ ]
980+ },
981+ "functors": {
982+ "type": "registry",
983+ "version": "5.0.0",
984+ "integrity": "sha256-W1o/4wcp6S3chodYL8uixlXK2mfr1L+SzM1R+whqUco=",
985+ "dependencies": [
986+ "bifunctors",
987+ "const",
988+ "contravariant",
989+ "control",
990+ "distributive",
991+ "either",
992+ "invariant",
993+ "maybe",
994+ "newtype",
995+ "prelude",
996+ "profunctor",
997+ "tuples",
998+ "unsafe-coerce"
999+ ]
1000+ },
1001+ "gen": {
1002+ "type": "registry",
1003+ "version": "4.0.0",
1004+ "integrity": "sha256-kyOateeOtQa07vfTMYeSuCKdelgKE1R65fwKkZ10wsY=",
1005+ "dependencies": [
1006+ "either",
1007+ "foldable-traversable",
1008+ "identity",
1009+ "maybe",
1010+ "newtype",
1011+ "nonempty",
1012+ "prelude",
1013+ "tailrec",
1014+ "tuples",
1015+ "unfoldable"
1016+ ]
1017+ },
1018+ "heterogeneous": {
1019+ "type": "registry",
1020+ "version": "0.7.0",
1021+ "integrity": "sha256-pQwcFvhWL8noktxaoD7FlLs2F2lYf3QHoyO0VOWO3I0=",
1022+ "dependencies": [
1023+ "either",
1024+ "foldable-traversable",
1025+ "functors",
1026+ "prelude",
1027+ "record",
1028+ "tuples",
1029+ "variant"
1030+ ]
1031+ },
1032+ "http-methods": {
1033+ "type": "registry",
1034+ "version": "6.0.0",
1035+ "integrity": "sha256-S0UjrJSgwJxEsFHM/pX77dM8V4HfvvDqdHPckQrtxMs=",
1036+ "dependencies": [
1037+ "either",
1038+ "prelude",
1039+ "strings"
1040+ ]
1041+ },
1042+ "identity": {
1043+ "type": "registry",
1044+ "version": "6.0.0",
1045+ "integrity": "sha256-RY/iXPpxpqvKHYfJeFVNI1J06kohB9rbtWYuha8t0LE=",
1046+ "dependencies": [
1047+ "control",
1048+ "invariant",
1049+ "newtype",
1050+ "prelude"
1051+ ]
1052+ },
1053+ "integers": {
1054+ "type": "registry",
1055+ "version": "6.0.0",
1056+ "integrity": "sha256-Y8yozC1uHRLcruobEyv5/bMKzE1BoKJ/olHv6o+bgm8=",
1057+ "dependencies": [
1058+ "maybe",
1059+ "numbers",
1060+ "prelude"
1061+ ]
1062+ },
1063+ "invariant": {
1064+ "type": "registry",
1065+ "version": "6.0.0",
1066+ "integrity": "sha256-HyoT1I5qIHRq8RSZji6zZDvqnpVjLw748utrKwZtqsw=",
1067+ "dependencies": [
1068+ "control",
1069+ "prelude"
1070+ ]
1071+ },
1072+ "js-bigints": {
1073+ "type": "registry",
1074+ "version": "2.2.1",
1075+ "integrity": "sha256-r1rE6Olv5b3JrP32lY2uKJ+eBRTsrVnC983K8VWwqe4=",
1076+ "dependencies": [
1077+ "integers",
1078+ "maybe",
1079+ "prelude"
1080+ ]
1081+ },
1082+ "js-date": {
1083+ "type": "registry",
1084+ "version": "8.0.0",
1085+ "integrity": "sha256-DNQrp4xYc8o80jtxo0aOkUeXGi6h0Xidkk2ZQ8Y4l5Y=",
1086+ "dependencies": [
1087+ "datetime",
1088+ "effect",
1089+ "enums",
1090+ "foreign",
1091+ "functions",
1092+ "integers",
1093+ "maybe",
1094+ "prelude"
1095+ ]
1096+ },
1097+ "js-promise": {
1098+ "type": "registry",
1099+ "version": "1.0.0",
1100+ "integrity": "sha256-u+6+ofIwT5XhT2fbMu4dShwkyWGfPRt9642Cnzw/Wvk=",
1101+ "dependencies": [
1102+ "effect",
1103+ "exceptions",
1104+ "foldable-traversable",
1105+ "functions",
1106+ "maybe",
1107+ "newtype",
1108+ "prelude"
1109+ ]
1110+ },
1111+ "js-promise-aff": {
1112+ "type": "registry",
1113+ "version": "1.0.0",
1114+ "integrity": "sha256-gEKAdNnTWjXS+tTp/AhYl/ezloKfuoQFcBKrdOx2USo=",
1115+ "dependencies": [
1116+ "aff",
1117+ "control",
1118+ "effect",
1119+ "either",
1120+ "exceptions",
1121+ "foreign",
1122+ "js-promise",
1123+ "maybe",
1124+ "prelude",
1125+ "transformers"
1126+ ]
1127+ },
1128+ "lazy": {
1129+ "type": "registry",
1130+ "version": "6.0.0",
1131+ "integrity": "sha256-MHRC+1Pc+AjortBp4b/e1e6KpEpsDwaJBNBB7TOL+1I=",
1132+ "dependencies": [
1133+ "control",
1134+ "foldable-traversable",
1135+ "invariant",
1136+ "prelude"
1137+ ]
1138+ },
1139+ "lists": {
1140+ "type": "registry",
1141+ "version": "7.0.0",
1142+ "integrity": "sha256-/mGd7wLoU+wsTcqii7SSUByxsvwvjnu2PjwAD47yYb4=",
1143+ "dependencies": [
1144+ "bifunctors",
1145+ "control",
1146+ "foldable-traversable",
1147+ "lazy",
1148+ "maybe",
1149+ "newtype",
1150+ "nonempty",
1151+ "partial",
1152+ "prelude",
1153+ "tailrec",
1154+ "tuples",
1155+ "unfoldable"
1156+ ]
1157+ },
1158+ "maybe": {
1159+ "type": "registry",
1160+ "version": "6.0.0",
1161+ "integrity": "sha256-8X6XEgtZ5lqfkEuc1hWOofnL6mwhk8gSJTxwvbR/SbY=",
1162+ "dependencies": [
1163+ "control",
1164+ "invariant",
1165+ "newtype",
1166+ "prelude"
1167+ ]
1168+ },
1169+ "media-types": {
1170+ "type": "registry",
1171+ "version": "6.0.0",
1172+ "integrity": "sha256-3Wyq+vfqJFTmy1k6DGVnXStxtZxgK631wusDPzlFVxg=",
1173+ "dependencies": [
1174+ "newtype",
1175+ "prelude"
1176+ ]
1177+ },
1178+ "newtype": {
1179+ "type": "registry",
1180+ "version": "5.0.0",
1181+ "integrity": "sha256-dt6w0cty4OS+Pjt9fsh3iU6ottqSU83mdAZkaEEvo6c=",
1182+ "dependencies": [
1183+ "prelude",
1184+ "safe-coerce"
1185+ ]
1186+ },
1187+ "nonempty": {
1188+ "type": "registry",
1189+ "version": "7.0.0",
1190+ "integrity": "sha256-Ctkq8/+KiGqCE53Y/LvibA/coy7ev9HtRZU+GYS7yfQ=",
1191+ "dependencies": [
1192+ "control",
1193+ "foldable-traversable",
1194+ "maybe",
1195+ "prelude",
1196+ "tuples",
1197+ "unfoldable"
1198+ ]
1199+ },
1200+ "nullable": {
1201+ "type": "registry",
1202+ "version": "6.0.0",
1203+ "integrity": "sha256-CDAZk+L9Sc0GCFTkE8n+qdp4Ys8avvnkU+m2hVALt7M=",
1204+ "dependencies": [
1205+ "functions",
1206+ "maybe",
1207+ "prelude"
1208+ ]
1209+ },
1210+ "numbers": {
1211+ "type": "registry",
1212+ "version": "9.0.1",
1213+ "integrity": "sha256-ZydYckgwRAnRaFC7pu6fADhzdwX+UqHZrfJyet64zls=",
1214+ "dependencies": [
1215+ "functions",
1216+ "maybe",
1217+ "prelude"
1218+ ]
1219+ },
1220+ "ordered-collections": {
1221+ "type": "registry",
1222+ "version": "3.2.0",
1223+ "integrity": "sha256-NaEXc2cz/7lJfxPWEja4XlZbGxuRqJwFKQJjsJf51kQ=",
1224+ "dependencies": [
1225+ "arrays",
1226+ "control",
1227+ "foldable-traversable",
1228+ "functions",
1229+ "gen",
1230+ "lists",
1231+ "maybe",
1232+ "newtype",
1233+ "partial",
1234+ "prelude",
1235+ "safe-coerce",
1236+ "tailrec",
1237+ "tuples",
1238+ "unfoldable"
1239+ ]
1240+ },
1241+ "orders": {
1242+ "type": "registry",
1243+ "version": "6.0.0",
1244+ "integrity": "sha256-nSMxZK7wxyDULkjZqvMyB71mVo6b0AyLLHosDVbv1XM=",
1245+ "dependencies": [
1246+ "newtype",
1247+ "prelude"
1248+ ]
1249+ },
1250+ "parallel": {
1251+ "type": "registry",
1252+ "version": "7.0.0",
1253+ "integrity": "sha256-bAGpVhrFz2IyHkA4MR7+tE/19FZ7dzYBdYPgK5svkqw=",
1254+ "dependencies": [
1255+ "control",
1256+ "effect",
1257+ "either",
1258+ "foldable-traversable",
1259+ "functors",
1260+ "maybe",
1261+ "newtype",
1262+ "prelude",
1263+ "profunctor",
1264+ "refs",
1265+ "transformers"
1266+ ]
1267+ },
1268+ "partial": {
1269+ "type": "registry",
1270+ "version": "4.0.0",
1271+ "integrity": "sha256-mdrlJBAgFMh79hNRW39uv7c+BwnnaOrAMmdh7+VhEhs=",
1272+ "dependencies": []
1273+ },
1274+ "prelude": {
1275+ "type": "registry",
1276+ "version": "6.0.2",
1277+ "integrity": "sha256-IqykrDRquGUD/LRSad7y75hZv3bUsGQ4knc8tdez8Qw=",
1278+ "dependencies": []
1279+ },
1280+ "profunctor": {
1281+ "type": "registry",
1282+ "version": "6.0.1",
1283+ "integrity": "sha256-Ow0mJC+PIIRq/a3mzCWY5vxuTgR/e+Ee68+mWeSRnWY=",
1284+ "dependencies": [
1285+ "control",
1286+ "distributive",
1287+ "either",
1288+ "exists",
1289+ "invariant",
1290+ "newtype",
1291+ "prelude",
1292+ "tuples"
1293+ ]
1294+ },
1295+ "random": {
1296+ "type": "registry",
1297+ "version": "6.0.0",
1298+ "integrity": "sha256-7sZRo6P9UIiXDZ9s2QL9T8h3wlIwLdQrR4+oZKOijQw=",
1299+ "dependencies": [
1300+ "effect",
1301+ "integers",
1302+ "prelude"
1303+ ]
1304+ },
1305+ "record": {
1306+ "type": "registry",
1307+ "version": "4.0.0",
1308+ "integrity": "sha256-WQ8fqp4X1wTA70J4qUcPW4Z7DCwx6e5KJnT/EuTdWSc=",
1309+ "dependencies": [
1310+ "functions",
1311+ "prelude",
1312+ "unsafe-coerce"
1313+ ]
1314+ },
1315+ "record-studio": {
1316+ "type": "registry",
1317+ "version": "1.0.4",
1318+ "integrity": "sha256-CRnFvSXpDDzio+buibUxFU9zPZDd7/lzrYC1Ya3FuIw=",
1319+ "dependencies": [
1320+ "heterogeneous",
1321+ "lists",
1322+ "prelude",
1323+ "record",
1324+ "typelevel-prelude",
1325+ "unsafe-coerce"
1326+ ]
1327+ },
1328+ "ref-lifted": {
1329+ "type": "local",
1330+ "path": "/Users/mark/code/purescript-ref-lifted",
1331+ "dependencies": [
1332+ "effect",
1333+ "prelude",
1334+ "refs"
1335+ ]
1336+ },
1337+ "refs": {
1338+ "type": "registry",
1339+ "version": "6.0.0",
1340+ "integrity": "sha256-KKD5G9dR2SauuDGULUJsxjqDOEi1Jqm+2Sq5U2mY8YU=",
1341+ "dependencies": [
1342+ "effect",
1343+ "prelude"
1344+ ]
1345+ },
1346+ "safe-coerce": {
1347+ "type": "registry",
1348+ "version": "2.0.0",
1349+ "integrity": "sha256-EJrxtKt5xC7TYjBZrSBO/J7Aw3DmtilYjt4olpAXWVc=",
1350+ "dependencies": [
1351+ "unsafe-coerce"
1352+ ]
1353+ },
1354+ "st": {
1355+ "type": "registry",
1356+ "version": "6.2.0",
1357+ "integrity": "sha256-WI4MEkkwUd4pnwZQ3G8VKWgX5t1RSNJludm5e9boaRo=",
1358+ "dependencies": [
1359+ "effect",
1360+ "partial",
1361+ "prelude",
1362+ "tailrec",
1363+ "unsafe-coerce"
1364+ ]
1365+ },
1366+ "strings": {
1367+ "type": "registry",
1368+ "version": "6.0.1",
1369+ "integrity": "sha256-FNto2hoNW5Da6W6crIk77YXMagBvOGHZE1kO7eZ1vLM=",
1370+ "dependencies": [
1371+ "arrays",
1372+ "control",
1373+ "either",
1374+ "enums",
1375+ "foldable-traversable",
1376+ "gen",
1377+ "integers",
1378+ "maybe",
1379+ "newtype",
1380+ "nonempty",
1381+ "partial",
1382+ "prelude",
1383+ "tailrec",
1384+ "tuples",
1385+ "unfoldable",
1386+ "unsafe-coerce"
1387+ ]
1388+ },
1389+ "tailrec": {
1390+ "type": "registry",
1391+ "version": "6.1.0",
1392+ "integrity": "sha256-xrorCfP0xV1wubs27idQCHQqdvSUuqmmL/am7Ji1wVU=",
1393+ "dependencies": [
1394+ "bifunctors",
1395+ "effect",
1396+ "either",
1397+ "identity",
1398+ "maybe",
1399+ "partial",
1400+ "prelude",
1401+ "refs"
1402+ ]
1403+ },
1404+ "transformers": {
1405+ "type": "registry",
1406+ "version": "6.1.0",
1407+ "integrity": "sha256-QJmgNT/y7ljPHSsXaW4zxF/982BFiQ90KNL1g/NtEOQ=",
1408+ "dependencies": [
1409+ "control",
1410+ "distributive",
1411+ "effect",
1412+ "either",
1413+ "exceptions",
1414+ "foldable-traversable",
1415+ "identity",
1416+ "lazy",
1417+ "maybe",
1418+ "newtype",
1419+ "prelude",
1420+ "st",
1421+ "tailrec",
1422+ "tuples",
1423+ "unfoldable"
1424+ ]
1425+ },
1426+ "tuples": {
1427+ "type": "registry",
1428+ "version": "7.0.0",
1429+ "integrity": "sha256-BKy7EfK8S1KeTCwyWqyW1wwcpSAQuh3IsczSVgS6fzY=",
1430+ "dependencies": [
1431+ "control",
1432+ "invariant",
1433+ "prelude"
1434+ ]
1435+ },
1436+ "type-equality": {
1437+ "type": "registry",
1438+ "version": "4.0.1",
1439+ "integrity": "sha256-BBqYOSnAKmayRvR5mtZoCf4SlYs2ipatD/+5nQx73aw=",
1440+ "dependencies": []
1441+ },
1442+ "typelevel-prelude": {
1443+ "type": "registry",
1444+ "version": "7.0.0",
1445+ "integrity": "sha256-+pHi14/40mTj/+lmCWqL0T7iXIXD1+NSg/KItyyoB0A=",
1446+ "dependencies": [
1447+ "prelude",
1448+ "type-equality"
1449+ ]
1450+ },
1451+ "uncurried-transformers": {
1452+ "type": "registry",
1453+ "version": "1.1.0",
1454+ "integrity": "sha256-AqqBMzVLzozVoDorE3QcVVRDkLd7O3624mRHCDnPhaI=",
1455+ "dependencies": [
1456+ "control",
1457+ "effect",
1458+ "either",
1459+ "functions",
1460+ "identity",
1461+ "prelude",
1462+ "safe-coerce",
1463+ "tailrec",
1464+ "transformers",
1465+ "tuples"
1466+ ]
1467+ },
1468+ "unfoldable": {
1469+ "type": "registry",
1470+ "version": "6.0.0",
1471+ "integrity": "sha256-BK/6By1sKp/ztk+9CF3q43SV0QDTLVdsGqFerKHSmbg=",
1472+ "dependencies": [
1473+ "foldable-traversable",
1474+ "maybe",
1475+ "partial",
1476+ "prelude",
1477+ "tuples"
1478+ ]
1479+ },
1480+ "unsafe-coerce": {
1481+ "type": "registry",
1482+ "version": "6.0.0",
1483+ "integrity": "sha256-0L1QsaY20OILjfU5TV72d3U/tSjhmL9hJ32WMp857Lk=",
1484+ "dependencies": []
1485+ },
1486+ "variant": {
1487+ "type": "registry",
1488+ "version": "8.0.0",
1489+ "integrity": "sha256-Ydrgl8liWEwYQOWW76PdZtg3vxKpoKgsnmFyTLgZkoU=",
1490+ "dependencies": [
1491+ "control",
1492+ "enums",
1493+ "foldable-traversable",
1494+ "lists",
1495+ "maybe",
1496+ "partial",
1497+ "prelude",
1498+ "type-equality",
1499+ "unsafe-coerce"
1500+ ]
1501+ },
1502+ "web-events": {
1503+ "type": "registry",
1504+ "version": "4.0.0",
1505+ "integrity": "sha256-wZNeldG0OU+8qGycUJ9e6vxf1/GC4Ep4OlRMhvjs9LA=",
1506+ "dependencies": [
1507+ "datetime",
1508+ "effect",
1509+ "enums",
1510+ "foreign",
1511+ "functions",
1512+ "maybe",
1513+ "newtype",
1514+ "nullable",
1515+ "prelude",
1516+ "unsafe-coerce"
1517+ ]
1518+ },
1519+ "web-fetch": {
1520+ "type": "registry",
1521+ "version": "4.0.1",
1522+ "integrity": "sha256-04+Kgtwk6BYT9WJfmIkYtwgXbfe8TDKP0tTBMcu+jBw=",
1523+ "dependencies": [
1524+ "arraybuffer-types",
1525+ "arrays",
1526+ "effect",
1527+ "foldable-traversable",
1528+ "foreign-object",
1529+ "functions",
1530+ "http-methods",
1531+ "js-promise",
1532+ "maybe",
1533+ "newtype",
1534+ "nullable",
1535+ "prelude",
1536+ "record",
1537+ "tuples",
1538+ "typelevel-prelude",
1539+ "unfoldable",
1540+ "web-file",
1541+ "web-streams"
1542+ ]
1543+ },
1544+ "web-file": {
1545+ "type": "registry",
1546+ "version": "4.0.0",
1547+ "integrity": "sha256-txsdkeWBKU/+5v9m5c6kysgJQSDyV1r7MHaeT4STpjQ=",
1548+ "dependencies": [
1549+ "datetime",
1550+ "effect",
1551+ "enums",
1552+ "foreign",
1553+ "integers",
1554+ "maybe",
1555+ "media-types",
1556+ "nullable",
1557+ "numbers",
1558+ "partial",
1559+ "prelude",
1560+ "tuples",
1561+ "unfoldable",
1562+ "unsafe-coerce",
1563+ "web-events"
1564+ ]
1565+ },
1566+ "web-streams": {
1567+ "type": "registry",
1568+ "version": "4.0.0",
1569+ "integrity": "sha256-iKQbHh/3wNOWyGTZmWJZ1Yuri9J/wQIJKXcxrELaHVo=",
1570+ "dependencies": [
1571+ "arraybuffer-types",
1572+ "effect",
1573+ "exceptions",
1574+ "js-promise",
1575+ "maybe",
1576+ "nullable",
1577+ "prelude",
1578+ "tuples"
1579+ ]
1580+ },
1581+ "yoga-bun-yoga": {
1582+ "type": "local",
1583+ "path": "/Users/mark/code/purescript-yoga-bun-yoga",
1584+ "dependencies": [
1585+ "aff",
1586+ "effect",
1587+ "either",
1588+ "exceptions",
1589+ "foreign",
1590+ "foreign-object",
1591+ "js-promise",
1592+ "js-promise-aff",
1593+ "prelude",
1594+ "web-fetch",
1595+ "yoga-json"
1596+ ]
1597+ },
1598+ "yoga-json": {
1599+ "type": "registry",
1600+ "version": "5.2.0",
1601+ "integrity": "sha256-M+kkz6E1Gf+C4bKnhqoBFIoVcc74loi3rLULePbB204=",
1602+ "dependencies": [
1603+ "arrays",
1604+ "bifunctors",
1605+ "control",
1606+ "datetime",
1607+ "effect",
1608+ "either",
1609+ "exceptions",
1610+ "foldable-traversable",
1611+ "foreign",
1612+ "foreign-object",
1613+ "free",
1614+ "identity",
1615+ "integers",
1616+ "js-bigints",
1617+ "js-date",
1618+ "lists",
1619+ "maybe",
1620+ "newtype",
1621+ "nullable",
1622+ "numbers",
1623+ "ordered-collections",
1624+ "partial",
1625+ "prelude",
1626+ "record",
1627+ "safe-coerce",
1628+ "strings",
1629+ "transformers",
1630+ "tuples",
1631+ "typelevel-prelude",
1632+ "unsafe-coerce",
1633+ "variant",
1634+ "yoga-tree"
1635+ ]
1636+ },
1637+ "yoga-om": {
1638+ "type": "local",
1639+ "path": "/Users/mark/code/purescript-yoga-om",
1640+ "dependencies": [
1641+ "aff",
1642+ "aff-retry",
1643+ "avar",
1644+ "console",
1645+ "control",
1646+ "datetime",
1647+ "effect",
1648+ "either",
1649+ "exceptions",
1650+ "functions",
1651+ "maybe",
1652+ "newtype",
1653+ "parallel",
1654+ "prelude",
1655+ "record",
1656+ "record-studio",
1657+ "ref-lifted",
1658+ "refs",
1659+ "tailrec",
1660+ "transformers",
1661+ "tuples",
1662+ "typelevel-prelude",
1663+ "uncurried-transformers",
1664+ "unsafe-coerce",
1665+ "variant"
1666+ ]
1667+ },
1668+ "yoga-om-layer": {
1669+ "type": "local",
1670+ "path": "/Users/mark/code/purescript-yoga-om-layer",
1671+ "dependencies": [
1672+ "aff",
1673+ "aff-retry",
1674+ "arrays",
1675+ "effect",
1676+ "exceptions",
1677+ "foldable-traversable",
1678+ "maybe",
1679+ "nullable",
1680+ "ordered-collections",
1681+ "parallel",
1682+ "prelude",
1683+ "record",
1684+ "record-studio",
1685+ "ref-lifted",
1686+ "refs",
1687+ "transformers",
1688+ "type-equality",
1689+ "typelevel-prelude",
1690+ "unsafe-coerce",
1691+ "variant",
1692+ "yoga-om"
1693+ ]
1694+ },
1695+ "yoga-sql-types": {
1696+ "type": "local",
1697+ "path": "/Users/mark/code/purescript-yoga-sql-types",
1698+ "dependencies": [
1699+ "arrays",
1700+ "bifunctors",
1701+ "either",
1702+ "foldable-traversable",
1703+ "foreign",
1704+ "foreign-object",
1705+ "heterogeneous",
1706+ "identity",
1707+ "lists",
1708+ "maybe",
1709+ "newtype",
1710+ "nullable",
1711+ "ordered-collections",
1712+ "partial",
1713+ "prelude",
1714+ "record",
1715+ "safe-coerce",
1716+ "transformers",
1717+ "tuples",
1718+ "typelevel-prelude",
1719+ "unsafe-coerce",
1720+ "yoga-json"
1721+ ]
1722+ },
1723+ "yoga-sqlite": {
1724+ "type": "local",
1725+ "path": "/Users/mark/code/purescript-yoga-sqlite",
1726+ "dependencies": [
1727+ "aff",
1728+ "arrays",
1729+ "effect",
1730+ "either",
1731+ "foldable-traversable",
1732+ "js-promise",
1733+ "js-promise-aff",
1734+ "maybe",
1735+ "newtype",
1736+ "nullable",
1737+ "prelude",
1738+ "unsafe-coerce",
1739+ "yoga-sql-types"
1740+ ]
1741+ },
1742+ "yoga-sqlite-om": {
1743+ "type": "local",
1744+ "path": "/Users/mark/code/purescript-yoga-sqlite-om",
1745+ "dependencies": [
1746+ "console",
1747+ "effect",
1748+ "prelude",
1749+ "yoga-om",
1750+ "yoga-om-layer",
1751+ "yoga-sqlite"
1752+ ]
1753+ },
1754+ "yoga-tree": {
1755+ "type": "registry",
1756+ "version": "1.0.0",
1757+ "integrity": "sha256-s8rIVEQkRi1VrMc92T9ceXcY0X6r5A4QqT6f7GgmDQI=",
1758+ "dependencies": [
1759+ "arrays",
1760+ "control",
1761+ "foldable-traversable",
1762+ "free",
1763+ "maybe",
1764+ "prelude",
1765+ "tailrec"
1766+ ]
1767+ }
1768+ }
1769+}
signal-backend/spago.yamlnew file+51
@@ -0,0 +1,51 @@
1+workspace:
2+ packageSet:
3+ registry: 73.0.0
4+ extraPackages:
5+ ref-lifted:
6+ path: /Users/mark/code/purescript-ref-lifted
7+ yoga-om:
8+ path: /Users/mark/code/purescript-yoga-om
9+ yoga-om-layer:
10+ path: /Users/mark/code/purescript-yoga-om-layer
11+ yoga-bun-yoga:
12+ path: /Users/mark/code/purescript-yoga-bun-yoga
13+ yoga-sqlite:
14+ path: /Users/mark/code/purescript-yoga-sqlite
15+ yoga-sqlite-om:
16+ path: /Users/mark/code/purescript-yoga-sqlite-om
17+ yoga-sql-types:
18+ path: /Users/mark/code/purescript-yoga-sql-types
19+ yoga-json: "5.2.0"
20+
21+package:
22+ name: signal-backend
23+ dependencies:
24+ - aff
25+ - arraybuffer-types
26+ - arrays
27+ - avar
28+ - console
29+ - effect
30+ - either
31+ - foldable-traversable
32+ - foreign
33+ - foreign-object
34+ - js-promise
35+ - js-promise-aff
36+ - maybe
37+ - newtype
38+ - nullable
39+ - prelude
40+ - refs
41+ - strings
42+ - tuples
43+ - unsafe-coerce
44+ - variant
45+ - web-fetch
46+ - yoga-json
47+ - yoga-om
48+ - yoga-om-layer
49+ - yoga-bun-yoga
50+ - yoga-sqlite
51+ - yoga-sqlite-om
signal-backend/src/Main.jsnew file+26
@@ -0,0 +1,26 @@
1+export const sendToWs = (ws) => (msg) => () => {
2+ ws.send(JSON.stringify({ type: "qrcode", url: msg }));
3+};
4+
5+export const serveWithWsImpl = (options) => {
6+ const server = Bun.serve({
7+ port: options.port,
8+ hostname: options.host,
9+ fetch(req, server) {
10+ if (server.upgrade(req)) return undefined;
11+ return new Response("Signal Backend", { status: 200 });
12+ },
13+ websocket: {
14+ open(ws) {
15+ options.wsOpen(ws);
16+ },
17+ message(ws, msg) {
18+ options.wsMessage(ws, msg);
19+ },
20+ close(ws, code) {
21+ options.wsClose(ws, code);
22+ },
23+ },
24+ });
25+ return server;
26+};
signal-backend/src/Main.pursnew file+53
@@ -0,0 +1,53 @@
1+module Main where
2+
3+import Prelude
4+
5+import Data.Maybe (Maybe(..))
6+import Effect (Effect)
7+import Effect.Aff (launchAff_)
8+import Effect.Class (liftEffect)
9+import Effect.Console as Console
10+import Effect.Ref as Ref
11+import Effect.Uncurried (EffectFn1, EffectFn2, mkEffectFn1, mkEffectFn2, runEffectFn1)
12+import Foreign (Foreign)
13+import Signal.Net as Net
14+import Signal.Provisioning as Provisioning
15+
16+foreign import sendToWs :: Foreign -> String -> Effect Unit
17+foreign import serveWithWsImpl :: EffectFn1
18+ { port :: Int
19+ , host :: String
20+ , wsOpen :: EffectFn1 Foreign Unit
21+ , wsMessage :: EffectFn2 Foreign Foreign Unit
22+ , wsClose :: EffectFn2 Foreign Foreign Unit
23+ }
24+ Unit
25+
26+main :: Effect Unit
27+main = launchAff_ do
28+ liftEffect $ Console.log "Signal backend starting..."
29+
30+ net <- liftEffect $ Net.createNet Net.Production "SignalPureScript/0.1"
31+ wsClientRef <- liftEffect $ Ref.new (Nothing :: Maybe Foreign)
32+
33+ liftEffect $ runEffectFn1 serveWithWsImpl
34+ { port: 8765
35+ , host: "127.0.0.1"
36+ , wsOpen: mkEffectFn1 \ws -> do
37+ Console.log "WebSocket client connected"
38+ Ref.write (Just ws) wsClientRef
39+ launchAff_ do
40+ msg <- Provisioning.provision net \url -> liftEffect do
41+ Console.log $ "QR URL: " <> url
42+ mWs <- Ref.read wsClientRef
43+ case mWs of
44+ Just ws' -> sendToWs ws' url
45+ Nothing -> pure unit
46+ liftEffect $ Console.log $ "Provisioned! Number: " <> show msg.number
47+ , wsMessage: mkEffectFn2 \_ _ -> pure unit
48+ , wsClose: mkEffectFn2 \_ _ -> do
49+ Console.log "WebSocket client disconnected"
50+ Ref.write Nothing wsClientRef
51+ }
52+
53+ liftEffect $ Console.log "Server running on ws://127.0.0.1:8765"
signal-backend/src/Signal/Keys.jsnew file+15
@@ -0,0 +1,15 @@
1+import {
2+ PrivateKey,
3+ PublicKey,
4+} from "@signalapp/libsignal-client";
5+
6+export const generatePrivateKeyImpl = () => PrivateKey.generate();
7+
8+export const getPublicKeyImpl = (privateKey) => privateKey.getPublicKey();
9+
10+export const serializePublicKeyImpl = (publicKey) => publicKey.serialize();
11+
12+export const agreeImpl = ({ privateKey, otherKey }) =>
13+ privateKey.agree(otherKey);
14+
15+export const deserializePublicKeyImpl = (buf) => PublicKey.deserialize(buf);
signal-backend/src/Signal/Keys.pursnew file+32
@@ -0,0 +1,32 @@
1+module Signal.Keys where
2+
3+import Prelude
4+
5+import Effect (Effect)
6+import Effect.Uncurried (EffectFn1, runEffectFn1)
7+import Data.ArrayBuffer.Types (Uint8Array)
8+
9+foreign import data PrivateKey :: Type
10+foreign import data PublicKey :: Type
11+foreign import data IdentityKeyPair :: Type
12+
13+foreign import generatePrivateKeyImpl :: Effect PrivateKey
14+foreign import getPublicKeyImpl :: EffectFn1 PrivateKey PublicKey
15+foreign import serializePublicKeyImpl :: EffectFn1 PublicKey Uint8Array
16+foreign import agreeImpl :: EffectFn1 { privateKey :: PrivateKey, otherKey :: PublicKey } Uint8Array
17+foreign import deserializePublicKeyImpl :: EffectFn1 Uint8Array PublicKey
18+
19+generatePrivateKey :: Effect PrivateKey
20+generatePrivateKey = generatePrivateKeyImpl
21+
22+getPublicKey :: PrivateKey -> Effect PublicKey
23+getPublicKey = runEffectFn1 getPublicKeyImpl
24+
25+serializePublicKey :: PublicKey -> Effect Uint8Array
26+serializePublicKey = runEffectFn1 serializePublicKeyImpl
27+
28+agree :: PrivateKey -> PublicKey -> Effect Uint8Array
29+agree privateKey otherKey = runEffectFn1 agreeImpl { privateKey, otherKey }
30+
31+deserializePublicKey :: Uint8Array -> Effect PublicKey
32+deserializePublicKey = runEffectFn1 deserializePublicKeyImpl
signal-backend/src/Signal/Net.jsnew file+15
@@ -0,0 +1,15 @@
1+import * as SignalNet from "@signalapp/libsignal-client/dist/net.js";
2+
3+export const createNetImpl = ({ env, userAgent }) =>
4+ new SignalNet.Net({ env, userAgent });
5+
6+export const connectProvisioningImpl = (net, listener) =>
7+ net.connectProvisioning({
8+ onReceivedAddress: (address, ack) => listener.onReceivedAddress(address, ack),
9+ onReceivedEnvelope: (envelope, ack) => listener.onReceivedEnvelope(envelope, ack),
10+ onConnectionInterrupted: (cause) => listener.onConnectionInterrupted(cause || {}),
11+ });
12+
13+export const disconnectProvisioningImpl = (conn) => conn.disconnect();
14+
15+export const ackImpl = (ack, statusCode) => ack.send(statusCode);
signal-backend/src/Signal/Net.pursnew file+56
@@ -0,0 +1,56 @@
1+module Signal.Net where
2+
3+import Prelude
4+
5+import Data.ArrayBuffer.Types (Uint8Array)
6+import Effect (Effect)
7+import Effect.Aff (Aff)
8+import Effect.Uncurried (EffectFn1, EffectFn2, mkEffectFn1, mkEffectFn2, runEffectFn1, runEffectFn2)
9+import Promise (Promise)
10+import Promise.Aff (toAffE) as Promise
11+
12+foreign import data SignalNet :: Type
13+foreign import data ProvisioningConnection :: Type
14+foreign import data ChatServerMessageAck :: Type
15+
16+data Environment = Staging | Production
17+
18+foreign import createNetImpl :: EffectFn1 { env :: Int, userAgent :: String } SignalNet
19+
20+createNet :: Environment -> String -> Effect SignalNet
21+createNet env userAgent = runEffectFn1 createNetImpl { env: envToInt env, userAgent }
22+ where
23+ envToInt Staging = 0
24+ envToInt Production = 1
25+
26+type ProvisioningListenerImpl =
27+ { onReceivedAddress :: EffectFn2 String ChatServerMessageAck Unit
28+ , onReceivedEnvelope :: EffectFn2 Uint8Array ChatServerMessageAck Unit
29+ , onConnectionInterrupted :: EffectFn1 {} Unit
30+ }
31+
32+type ProvisioningListener =
33+ { onReceivedAddress :: String -> ChatServerMessageAck -> Effect Unit
34+ , onReceivedEnvelope :: Uint8Array -> ChatServerMessageAck -> Effect Unit
35+ , onConnectionInterrupted :: Effect Unit
36+ }
37+
38+foreign import connectProvisioningImpl :: EffectFn2 SignalNet ProvisioningListenerImpl (Promise ProvisioningConnection)
39+foreign import disconnectProvisioningImpl :: EffectFn1 ProvisioningConnection (Promise Unit)
40+foreign import ackImpl :: EffectFn2 ChatServerMessageAck Int Unit
41+
42+connectProvisioning :: SignalNet -> ProvisioningListener -> Aff ProvisioningConnection
43+connectProvisioning net listener = do
44+ let
45+ listenerImpl =
46+ { onReceivedAddress: mkEffectFn2 listener.onReceivedAddress
47+ , onReceivedEnvelope: mkEffectFn2 listener.onReceivedEnvelope
48+ , onConnectionInterrupted: mkEffectFn1 \_ -> listener.onConnectionInterrupted
49+ }
50+ runEffectFn2 connectProvisioningImpl net listenerImpl # Promise.toAffE
51+
52+disconnectProvisioning :: ProvisioningConnection -> Aff Unit
53+disconnectProvisioning conn = runEffectFn1 disconnectProvisioningImpl conn # Promise.toAffE
54+
55+ack :: ChatServerMessageAck -> Int -> Effect Unit
56+ack = runEffectFn2 ackImpl
signal-backend/src/Signal/Provisioning.pursnew file+45
@@ -0,0 +1,45 @@
1+module Signal.Provisioning where
2+
3+import Prelude
4+
5+import Effect.Aff (Aff)
6+import Effect.Aff.AVar as AffAVar
7+import Effect.AVar as AVar
8+import Effect.Class (liftEffect)
9+import Effect.Console as Console
10+import Signal.Keys as Keys
11+import Signal.Net as Net
12+import Signal.Provisioning.Cipher (ProvisionMessage)
13+import Signal.Provisioning.Cipher as Cipher
14+import Signal.Provisioning.QRCode as QRCode
15+
16+provision :: Net.SignalNet -> (String -> Aff Unit) -> Aff ProvisionMessage
17+provision net onQRCode = do
18+ privateKey <- liftEffect Keys.generatePrivateKey
19+ publicKey <- liftEffect $ Keys.getPublicKey privateKey
20+ publicKeySerialized <- liftEffect $ Keys.serializePublicKey publicKey
21+ messageVar <- liftEffect AVar.empty
22+ urlVar <- liftEffect AVar.empty
23+
24+ let
25+ listener =
26+ { onReceivedAddress: \address ack -> do
27+ url <- QRCode.buildProvisioningUrl address publicKeySerialized
28+ Console.log "Provisioning: QR code ready"
29+ void $ AVar.tryPut url urlVar
30+ Net.ack ack 200
31+ , onReceivedEnvelope: \envelope ack -> do
32+ Console.log "Provisioning: decrypting envelope..."
33+ msg <- Cipher.decryptProvisionEnvelope envelope privateKey
34+ void $ AVar.tryPut msg messageVar
35+ Net.ack ack 200
36+ , onConnectionInterrupted:
37+ Console.log "Provisioning: connection closed"
38+ }
39+
40+ _conn <- Net.connectProvisioning net listener
41+
42+ url <- AffAVar.read urlVar
43+ onQRCode url
44+
45+ AffAVar.read messageVar
signal-backend/src/Signal/Provisioning/Cipher.jsnew file+144
@@ -0,0 +1,144 @@
1+import { PrivateKey, PublicKey, hkdf } from "@signalapp/libsignal-client";
2+import { createDecipheriv, createHmac } from "node:crypto";
3+
4+// Protobuf decoding for ProvisionEnvelope and ProvisionMessage
5+// These are simple enough to decode manually without a protobuf library
6+
7+function decodeVarint(buf, offset) {
8+ let result = 0;
9+ let shift = 0;
10+ let pos = offset;
11+ while (pos < buf.length) {
12+ const byte = buf[pos];
13+ result |= (byte & 0x7f) << shift;
14+ pos++;
15+ if ((byte & 0x80) === 0) break;
16+ shift += 7;
17+ }
18+ return { value: result, bytesRead: pos - offset };
19+}
20+
21+function decodeProtobuf(buf) {
22+ const fields = {};
23+ let pos = 0;
24+ while (pos < buf.length) {
25+ const { value: tag, bytesRead: tagBytes } = decodeVarint(buf, pos);
26+ pos += tagBytes;
27+ const fieldNumber = tag >>> 3;
28+ const wireType = tag & 0x7;
29+
30+ if (wireType === 0) {
31+ // varint
32+ const { value, bytesRead } = decodeVarint(buf, pos);
33+ pos += bytesRead;
34+ fields[fieldNumber] = value;
35+ } else if (wireType === 2) {
36+ // length-delimited
37+ const { value: len, bytesRead } = decodeVarint(buf, pos);
38+ pos += bytesRead;
39+ fields[fieldNumber] = buf.slice(pos, pos + len);
40+ pos += len;
41+ } else {
42+ break; // unknown wire type
43+ }
44+ }
45+ return fields;
46+}
47+
48+function decodeProvisionEnvelope(buf) {
49+ const fields = decodeProtobuf(buf);
50+ return {
51+ publicKey: fields[1] || null,
52+ body: fields[2] || null,
53+ };
54+}
55+
56+function decodeProvisionMessage(buf) {
57+ const fields = decodeProtobuf(buf);
58+ return {
59+ aciIdentityKeyPublic: fields[1] || null,
60+ aciIdentityKeyPrivate: fields[2] || null,
61+ number: fields[3] ? new TextDecoder().decode(fields[3]) : null,
62+ provisioningCode: fields[4]
63+ ? new TextDecoder().decode(fields[4])
64+ : null,
65+ userAgent: fields[5] ? new TextDecoder().decode(fields[5]) : null,
66+ profileKey: fields[6] || null,
67+ readReceipts: fields[7] || null,
68+ aci: fields[8] ? new TextDecoder().decode(fields[8]) : null,
69+ provisioningVersion: fields[9] ?? null,
70+ pni: fields[10] ? new TextDecoder().decode(fields[10]) : null,
71+ pniIdentityKeyPublic: fields[11] || null,
72+ pniIdentityKeyPrivate: fields[12] || null,
73+ masterKey: fields[13] || null,
74+ ephemeralBackupKey: fields[14] || null,
75+ accountEntropyPool: fields[15]
76+ ? new TextDecoder().decode(fields[15])
77+ : null,
78+ mediaRootBackupKey: fields[16] || null,
79+ };
80+}
81+
82+export const decryptProvisionEnvelopeImpl = ({ envelope, privateKey }) => {
83+ // 1. Decode the ProvisionEnvelope protobuf
84+ const env = decodeProvisionEnvelope(
85+ envelope instanceof Uint8Array ? envelope : new Uint8Array(envelope)
86+ );
87+ if (!env.publicKey || !env.body) {
88+ throw new Error("Invalid provision envelope: missing publicKey or body");
89+ }
90+
91+ // 2. Verify version byte
92+ const message = new Uint8Array(env.body);
93+ if (message[0] !== 1) {
94+ throw new Error(`Unsupported provisioning version: ${message[0]}`);
95+ }
96+
97+ // 3. Extract iv, ciphertext, mac
98+ const iv = message.slice(1, 17);
99+ const mac = message.slice(message.length - 32);
100+ const ivAndCiphertext = message.slice(0, message.length - 32);
101+ const ciphertext = message.slice(17, message.length - 32);
102+
103+ // 4. ECDH agreement
104+ const theirPublicKey = PublicKey.deserialize(Buffer.from(env.publicKey));
105+ const sharedSecret = privateKey.agree(theirPublicKey);
106+
107+ // 5. Derive keys via HKDF
108+ const salt = new Uint8Array(32); // 32 zero bytes
109+ const info = new TextEncoder().encode("TextSecure Provisioning Message");
110+ const derivedKeys = hkdf(96, sharedSecret, info, salt);
111+ const aesKey = derivedKeys.slice(0, 32);
112+ const hmacKey = derivedKeys.slice(32, 64);
113+
114+ // 6. Verify HMAC
115+ const hmac = createHmac("sha256", Buffer.from(hmacKey));
116+ hmac.update(Buffer.from(ivAndCiphertext));
117+ const computedMac = new Uint8Array(hmac.digest());
118+ if (!constantTimeEqual(computedMac, mac)) {
119+ throw new Error("HMAC verification failed");
120+ }
121+
122+ // 7. Decrypt AES-256-CBC
123+ const decipher = createDecipheriv(
124+ "aes-256-cbc",
125+ Buffer.from(aesKey),
126+ Buffer.from(iv)
127+ );
128+ const decrypted = Buffer.concat([
129+ decipher.update(Buffer.from(ciphertext)),
130+ decipher.final(),
131+ ]);
132+
133+ // 8. Decode ProvisionMessage protobuf
134+ return decodeProvisionMessage(new Uint8Array(decrypted));
135+};
136+
137+function constantTimeEqual(a, b) {
138+ if (a.length !== b.length) return false;
139+ let result = 0;
140+ for (let i = 0; i < a.length; i++) {
141+ result |= a[i] ^ b[i];
142+ }
143+ return result === 0;
144+}
signal-backend/src/Signal/Provisioning/Cipher.pursnew file+60
@@ -0,0 +1,60 @@
1+module Signal.Provisioning.Cipher where
2+
3+import Prelude
4+
5+import Data.ArrayBuffer.Types (Uint8Array)
6+import Data.Maybe (Maybe)
7+import Data.Nullable (Nullable, toMaybe)
8+import Effect (Effect)
9+import Effect.Uncurried (EffectFn1, runEffectFn1)
10+import Signal.Keys (PrivateKey)
11+
12+type ProvisionMessage =
13+ { aciIdentityKeyPublic :: Maybe Uint8Array
14+ , aciIdentityKeyPrivate :: Maybe Uint8Array
15+ , pniIdentityKeyPublic :: Maybe Uint8Array
16+ , pniIdentityKeyPrivate :: Maybe Uint8Array
17+ , number :: Maybe String
18+ , provisioningCode :: Maybe String
19+ , aci :: Maybe String
20+ , pni :: Maybe String
21+ , profileKey :: Maybe Uint8Array
22+ , masterKey :: Maybe Uint8Array
23+ , accountEntropyPool :: Maybe String
24+ , provisioningVersion :: Maybe Int
25+ }
26+
27+type ProvisionMessageImpl =
28+ { aciIdentityKeyPublic :: Nullable Uint8Array
29+ , aciIdentityKeyPrivate :: Nullable Uint8Array
30+ , pniIdentityKeyPublic :: Nullable Uint8Array
31+ , pniIdentityKeyPrivate :: Nullable Uint8Array
32+ , number :: Nullable String
33+ , provisioningCode :: Nullable String
34+ , aci :: Nullable String
35+ , pni :: Nullable String
36+ , profileKey :: Nullable Uint8Array
37+ , masterKey :: Nullable Uint8Array
38+ , accountEntropyPool :: Nullable String
39+ , provisioningVersion :: Nullable Int
40+ }
41+
42+foreign import decryptProvisionEnvelopeImpl :: EffectFn1 { envelope :: Uint8Array, privateKey :: PrivateKey } ProvisionMessageImpl
43+
44+decryptProvisionEnvelope :: Uint8Array -> PrivateKey -> Effect ProvisionMessage
45+decryptProvisionEnvelope envelope privateKey = do
46+ r <- runEffectFn1 decryptProvisionEnvelopeImpl { envelope, privateKey }
47+ pure
48+ { aciIdentityKeyPublic: toMaybe r.aciIdentityKeyPublic
49+ , aciIdentityKeyPrivate: toMaybe r.aciIdentityKeyPrivate
50+ , pniIdentityKeyPublic: toMaybe r.pniIdentityKeyPublic
51+ , pniIdentityKeyPrivate: toMaybe r.pniIdentityKeyPrivate
52+ , number: toMaybe r.number
53+ , provisioningCode: toMaybe r.provisioningCode
54+ , aci: toMaybe r.aci
55+ , pni: toMaybe r.pni
56+ , profileKey: toMaybe r.profileKey
57+ , masterKey: toMaybe r.masterKey
58+ , accountEntropyPool: toMaybe r.accountEntropyPool
59+ , provisioningVersion: toMaybe r.provisioningVersion
60+ }
signal-backend/src/Signal/Provisioning/QRCode.jsnew file+9
@@ -0,0 +1,9 @@
1+export const buildProvisioningUrlImpl = ({ address, publicKeySerialized }) => {
2+ const pubKeyBase64 = Buffer.from(publicKeySerialized).toString("base64");
3+ const params = new URLSearchParams({
4+ uuid: address,
5+ pub_key: pubKeyBase64,
6+ capabilities: "",
7+ });
8+ return `sgnl://linkdevice?${params.toString()}`;
9+};
signal-backend/src/Signal/Provisioning/QRCode.pursnew file+13
@@ -0,0 +1,13 @@
1+module Signal.Provisioning.QRCode where
2+
3+import Prelude
4+
5+import Data.ArrayBuffer.Types (Uint8Array)
6+import Effect (Effect)
7+import Effect.Uncurried (EffectFn1, runEffectFn1)
8+
9+foreign import buildProvisioningUrlImpl :: EffectFn1 { address :: String, publicKeySerialized :: Uint8Array } String
10+
11+buildProvisioningUrl :: String -> Uint8Array -> Effect String
12+buildProvisioningUrl address publicKeySerialized =
13+ runEffectFn1 buildProvisioningUrlImpl { address, publicKeySerialized }