its for when you want to get like notifications for your reposts

feat(webapp): split the app to prepare it for extension

ptr.pet 83b84cef 9fc61c80

verified
+630 -115
+16 -1
webapp/package.json
··· 1 1 { 2 - "name": "vite-template-solid", 2 + "name": "bsky-repost-likes-monitor", 3 3 "version": "0.0.0", 4 4 "description": "", 5 5 "type": "module", ··· 7 7 "start": "vite", 8 8 "dev": "vite", 9 9 "build": "vite build", 10 + "build:lib": "vite build --config vite.config.lib.ts", 10 11 "serve": "vite preview" 11 12 }, 12 13 "license": "MIT", 14 + "main": "./dist/index.js", 15 + "module": "./dist/index.js", 16 + "types": "./dist/index.d.ts", 17 + "exports": { 18 + ".": { 19 + "import": "./dist/index.js", 20 + "types": "./dist/index.d.ts" 21 + }, 22 + "./style.css": "./dist/index.css" 23 + }, 24 + "files": [ 25 + "dist" 26 + ], 13 27 "devDependencies": { 14 28 "@eslint/css": "^0.8.1", 15 29 "@eslint/js": "^9.28.0", ··· 23 37 "eslint-plugin-solid": "^0.14.5", 24 38 "globals": "^16.2.0", 25 39 "prettier": "3.5.3", 40 + "solid-devtools": "^0.34.3", 26 41 "typescript": "^5.7.2", 27 42 "typescript-eslint": "^8.33.1", 28 43 "unocss": "^66.1.4",
+374
webapp/pnpm-lock.yaml
··· 63 63 prettier: 64 64 specifier: 3.5.3 65 65 version: 3.5.3 66 + solid-devtools: 67 + specifier: ^0.34.3 68 + version: 0.34.3(solid-js@1.9.5)(vite@6.0.0(jiti@2.4.2)) 66 69 typescript: 67 70 specifier: ^5.7.2 68 71 version: 5.7.2 ··· 118 121 resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} 119 122 engines: {node: '>=6.9.0'} 120 123 124 + '@babel/code-frame@7.27.1': 125 + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} 126 + engines: {node: '>=6.9.0'} 127 + 121 128 '@babel/compat-data@7.26.2': 122 129 resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==} 123 130 engines: {node: '>=6.9.0'} 124 131 132 + '@babel/compat-data@7.28.0': 133 + resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} 134 + engines: {node: '>=6.9.0'} 135 + 125 136 '@babel/core@7.26.0': 126 137 resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} 127 138 engines: {node: '>=6.9.0'} 128 139 140 + '@babel/core@7.28.0': 141 + resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} 142 + engines: {node: '>=6.9.0'} 143 + 129 144 '@babel/generator@7.26.2': 130 145 resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} 146 + engines: {node: '>=6.9.0'} 147 + 148 + '@babel/generator@7.28.0': 149 + resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} 131 150 engines: {node: '>=6.9.0'} 132 151 133 152 '@babel/helper-compilation-targets@7.25.9': 134 153 resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} 135 154 engines: {node: '>=6.9.0'} 136 155 156 + '@babel/helper-compilation-targets@7.27.2': 157 + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} 158 + engines: {node: '>=6.9.0'} 159 + 160 + '@babel/helper-globals@7.28.0': 161 + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} 162 + engines: {node: '>=6.9.0'} 163 + 137 164 '@babel/helper-module-imports@7.18.6': 138 165 resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} 139 166 engines: {node: '>=6.9.0'} ··· 142 169 resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} 143 170 engines: {node: '>=6.9.0'} 144 171 172 + '@babel/helper-module-imports@7.27.1': 173 + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} 174 + engines: {node: '>=6.9.0'} 175 + 145 176 '@babel/helper-module-transforms@7.26.0': 146 177 resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} 147 178 engines: {node: '>=6.9.0'} 148 179 peerDependencies: 149 180 '@babel/core': ^7.0.0 150 181 182 + '@babel/helper-module-transforms@7.27.3': 183 + resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} 184 + engines: {node: '>=6.9.0'} 185 + peerDependencies: 186 + '@babel/core': ^7.0.0 187 + 151 188 '@babel/helper-plugin-utils@7.25.9': 152 189 resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} 190 + engines: {node: '>=6.9.0'} 191 + 192 + '@babel/helper-plugin-utils@7.27.1': 193 + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} 153 194 engines: {node: '>=6.9.0'} 154 195 155 196 '@babel/helper-string-parser@7.25.9': ··· 172 213 resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} 173 214 engines: {node: '>=6.9.0'} 174 215 216 + '@babel/helper-validator-option@7.27.1': 217 + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} 218 + engines: {node: '>=6.9.0'} 219 + 175 220 '@babel/helpers@7.26.0': 176 221 resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} 177 222 engines: {node: '>=6.9.0'} 178 223 224 + '@babel/helpers@7.27.6': 225 + resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==} 226 + engines: {node: '>=6.9.0'} 227 + 179 228 '@babel/parser@7.26.2': 180 229 resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} 181 230 engines: {node: '>=6.0.0'} ··· 183 232 184 233 '@babel/parser@7.27.5': 185 234 resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==} 235 + engines: {node: '>=6.0.0'} 236 + hasBin: true 237 + 238 + '@babel/parser@7.28.0': 239 + resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} 186 240 engines: {node: '>=6.0.0'} 187 241 hasBin: true 188 242 189 243 '@babel/plugin-syntax-jsx@7.25.9': 190 244 resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} 245 + engines: {node: '>=6.9.0'} 246 + peerDependencies: 247 + '@babel/core': ^7.0.0-0 248 + 249 + '@babel/plugin-syntax-typescript@7.27.1': 250 + resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} 191 251 engines: {node: '>=6.9.0'} 192 252 peerDependencies: 193 253 '@babel/core': ^7.0.0-0 ··· 196 256 resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} 197 257 engines: {node: '>=6.9.0'} 198 258 259 + '@babel/template@7.27.2': 260 + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} 261 + engines: {node: '>=6.9.0'} 262 + 199 263 '@babel/traverse@7.25.9': 200 264 resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} 201 265 engines: {node: '>=6.9.0'} 202 266 267 + '@babel/traverse@7.28.0': 268 + resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} 269 + engines: {node: '>=6.9.0'} 270 + 203 271 '@babel/types@7.26.0': 204 272 resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} 205 273 engines: {node: '>=6.9.0'} 206 274 207 275 '@babel/types@7.27.6': 208 276 resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==} 277 + engines: {node: '>=6.9.0'} 278 + 279 + '@babel/types@7.28.0': 280 + resolution: {integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==} 209 281 engines: {node: '>=6.9.0'} 210 282 211 283 '@badrap/valita@0.4.5': ··· 428 500 '@iconify/utils@2.3.0': 429 501 resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==} 430 502 503 + '@jridgewell/gen-mapping@0.3.12': 504 + resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} 505 + 431 506 '@jridgewell/gen-mapping@0.3.5': 432 507 resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} 433 508 engines: {node: '>=6.0.0'} ··· 446 521 '@jridgewell/trace-mapping@0.3.25': 447 522 resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} 448 523 524 + '@jridgewell/trace-mapping@0.3.29': 525 + resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} 526 + 449 527 '@nodelib/fs.scandir@2.1.5': 450 528 resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 451 529 engines: {node: '>= 8'} ··· 457 535 '@nodelib/fs.walk@1.2.8': 458 536 resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 459 537 engines: {node: '>= 8'} 538 + 539 + '@nothing-but/utils@0.17.0': 540 + resolution: {integrity: sha512-TuCHcHLOqDL0SnaAxACfuRHBNRgNJcNn9X0GiH5H3YSDBVquCr3qEIG3FOQAuMyZCbu9w8nk2CHhOsn7IvhIwQ==} 460 541 461 542 '@polka/url@1.0.0-next.29': 462 543 resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} ··· 555 636 cpu: [x64] 556 637 os: [win32] 557 638 639 + '@solid-devtools/debugger@0.28.1': 640 + resolution: {integrity: sha512-6qIUI6VYkXoRnL8oF5bvh2KgH71qlJ18hNw/mwSyY6v48eb80ZR48/5PDXufUa3q+MBSuYa1uqTMwLewpay9eg==} 641 + peerDependencies: 642 + solid-js: ^1.9.0 643 + 644 + '@solid-devtools/shared@0.20.0': 645 + resolution: {integrity: sha512-o5TACmUOQsxpzpOKCjbQqGk8wL8PMi+frXG9WNu4Lh3PQVUB6hs95Kl/S8xc++zwcMguUKZJn8h5URUiMOca6Q==} 646 + peerDependencies: 647 + solid-js: ^1.9.0 648 + 649 + '@solid-primitives/bounds@0.1.3': 650 + resolution: {integrity: sha512-UbiyKMdSPmtijcEDnYLQL3zzaejpwWDAJJ4Gt5P0hgVs6A72piov0GyNw7V2SroH7NZFwxlYS22YmOr8A5xc1Q==} 651 + peerDependencies: 652 + solid-js: ^1.6.12 653 + 654 + '@solid-primitives/event-listener@2.4.3': 655 + resolution: {integrity: sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg==} 656 + peerDependencies: 657 + solid-js: ^1.6.12 658 + 659 + '@solid-primitives/keyboard@1.3.3': 660 + resolution: {integrity: sha512-9dQHTTgLBqyAI7aavtO+HnpTVJgWQA1ghBSrmLtMu1SMxLPDuLfuNr+Tk5udb4AL4Ojg7h9JrKOGEEDqsJXWJA==} 661 + peerDependencies: 662 + solid-js: ^1.6.12 663 + 664 + '@solid-primitives/media@2.3.3': 665 + resolution: {integrity: sha512-hQ4hLOGvfbugQi5Eu1BFWAIJGIAzztq9x0h02xgBGl2l0Jaa3h7tg6bz5tV1NSuNYVGio4rPoa7zVQQLkkx9dA==} 666 + peerDependencies: 667 + solid-js: ^1.6.12 668 + 669 + '@solid-primitives/refs@1.1.2': 670 + resolution: {integrity: sha512-K7tf2thy7L+YJjdqXspXOg5xvNEOH8tgEWsp0+1mQk3obHBRD6hEjYZk7p7FlJphSZImS35je3UfmWuD7MhDfg==} 671 + peerDependencies: 672 + solid-js: ^1.6.12 673 + 674 + '@solid-primitives/resize-observer@2.1.3': 675 + resolution: {integrity: sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ==} 676 + peerDependencies: 677 + solid-js: ^1.6.12 678 + 679 + '@solid-primitives/rootless@1.5.2': 680 + resolution: {integrity: sha512-9HULb0QAzL2r47CCad0M+NKFtQ+LrGGNHZfteX/ThdGvKIg2o2GYhBooZubTCd/RTu2l2+Nw4s+dEfiDGvdrrQ==} 681 + peerDependencies: 682 + solid-js: ^1.6.12 683 + 684 + '@solid-primitives/scheduled@1.5.2': 685 + resolution: {integrity: sha512-/j2igE0xyNaHhj6kMfcUQn5rAVSTLbAX+CDEBm25hSNBmNiHLu2lM7Usj2kJJ5j36D67bE8wR1hBNA8hjtvsQA==} 686 + peerDependencies: 687 + solid-js: ^1.6.12 688 + 689 + '@solid-primitives/static-store@0.1.2': 690 + resolution: {integrity: sha512-ReK+5O38lJ7fT+L6mUFvUr6igFwHBESZF+2Ug842s7fvlVeBdIVEdTCErygff6w7uR6+jrr7J8jQo+cYrEq4Iw==} 691 + peerDependencies: 692 + solid-js: ^1.6.12 693 + 694 + '@solid-primitives/styles@0.1.2': 695 + resolution: {integrity: sha512-7iX5K+J5b1PRrbgw3Ki92uvU2LgQ0Kd/QMsrAZxDg5dpUBwMyTijZkA3bbs1ikZsT1oQhS41bTyKbjrXeU0Awg==} 696 + peerDependencies: 697 + solid-js: ^1.6.12 698 + 699 + '@solid-primitives/utils@6.3.2': 700 + resolution: {integrity: sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ==} 701 + peerDependencies: 702 + solid-js: ^1.6.12 703 + 558 704 '@types/babel__core@7.20.5': 559 705 resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} 560 706 ··· 1368 1514 resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} 1369 1515 engines: {node: '>=18'} 1370 1516 1517 + solid-devtools@0.34.3: 1518 + resolution: {integrity: sha512-ZQua959n+Zu3sLbm9g0IRjYUb1YYlYbu83PWLRoKbSsq0a3ItQNhnS2OBU7rQNmOKZiMexNo9Z3izas9BcOKDg==} 1519 + peerDependencies: 1520 + solid-js: ^1.9.0 1521 + vite: ^2.2.3 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 1522 + peerDependenciesMeta: 1523 + vite: 1524 + optional: true 1525 + 1371 1526 solid-js@1.9.5: 1372 1527 resolution: {integrity: sha512-ogI3DaFcyn6UhYhrgcyRAMbu/buBJitYQASZz5WzfQVPP10RD2AbCoRZ517psnezrasyCbWzIxZ6kVqet768xw==} 1373 1528 ··· 1603 1758 js-tokens: 4.0.0 1604 1759 picocolors: 1.1.1 1605 1760 1761 + '@babel/code-frame@7.27.1': 1762 + dependencies: 1763 + '@babel/helper-validator-identifier': 7.27.1 1764 + js-tokens: 4.0.0 1765 + picocolors: 1.1.1 1766 + 1606 1767 '@babel/compat-data@7.26.2': {} 1768 + 1769 + '@babel/compat-data@7.28.0': {} 1607 1770 1608 1771 '@babel/core@7.26.0': 1609 1772 dependencies: ··· 1625 1788 transitivePeerDependencies: 1626 1789 - supports-color 1627 1790 1791 + '@babel/core@7.28.0': 1792 + dependencies: 1793 + '@ampproject/remapping': 2.3.0 1794 + '@babel/code-frame': 7.27.1 1795 + '@babel/generator': 7.28.0 1796 + '@babel/helper-compilation-targets': 7.27.2 1797 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) 1798 + '@babel/helpers': 7.27.6 1799 + '@babel/parser': 7.28.0 1800 + '@babel/template': 7.27.2 1801 + '@babel/traverse': 7.28.0 1802 + '@babel/types': 7.28.0 1803 + convert-source-map: 2.0.0 1804 + debug: 4.4.1 1805 + gensync: 1.0.0-beta.2 1806 + json5: 2.2.3 1807 + semver: 6.3.1 1808 + transitivePeerDependencies: 1809 + - supports-color 1810 + 1628 1811 '@babel/generator@7.26.2': 1629 1812 dependencies: 1630 1813 '@babel/parser': 7.26.2 ··· 1633 1816 '@jridgewell/trace-mapping': 0.3.25 1634 1817 jsesc: 3.0.2 1635 1818 1819 + '@babel/generator@7.28.0': 1820 + dependencies: 1821 + '@babel/parser': 7.28.0 1822 + '@babel/types': 7.28.0 1823 + '@jridgewell/gen-mapping': 0.3.12 1824 + '@jridgewell/trace-mapping': 0.3.29 1825 + jsesc: 3.0.2 1826 + 1636 1827 '@babel/helper-compilation-targets@7.25.9': 1637 1828 dependencies: 1638 1829 '@babel/compat-data': 7.26.2 ··· 1641 1832 lru-cache: 5.1.1 1642 1833 semver: 6.3.1 1643 1834 1835 + '@babel/helper-compilation-targets@7.27.2': 1836 + dependencies: 1837 + '@babel/compat-data': 7.28.0 1838 + '@babel/helper-validator-option': 7.27.1 1839 + browserslist: 4.24.2 1840 + lru-cache: 5.1.1 1841 + semver: 6.3.1 1842 + 1843 + '@babel/helper-globals@7.28.0': {} 1844 + 1644 1845 '@babel/helper-module-imports@7.18.6': 1645 1846 dependencies: 1646 1847 '@babel/types': 7.26.0 ··· 1652 1853 transitivePeerDependencies: 1653 1854 - supports-color 1654 1855 1856 + '@babel/helper-module-imports@7.27.1': 1857 + dependencies: 1858 + '@babel/traverse': 7.28.0 1859 + '@babel/types': 7.28.0 1860 + transitivePeerDependencies: 1861 + - supports-color 1862 + 1655 1863 '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': 1656 1864 dependencies: 1657 1865 '@babel/core': 7.26.0 ··· 1661 1869 transitivePeerDependencies: 1662 1870 - supports-color 1663 1871 1872 + '@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)': 1873 + dependencies: 1874 + '@babel/core': 7.28.0 1875 + '@babel/helper-module-imports': 7.27.1 1876 + '@babel/helper-validator-identifier': 7.27.1 1877 + '@babel/traverse': 7.28.0 1878 + transitivePeerDependencies: 1879 + - supports-color 1880 + 1664 1881 '@babel/helper-plugin-utils@7.25.9': {} 1882 + 1883 + '@babel/helper-plugin-utils@7.27.1': {} 1665 1884 1666 1885 '@babel/helper-string-parser@7.25.9': {} 1667 1886 ··· 1673 1892 1674 1893 '@babel/helper-validator-option@7.25.9': {} 1675 1894 1895 + '@babel/helper-validator-option@7.27.1': {} 1896 + 1676 1897 '@babel/helpers@7.26.0': 1677 1898 dependencies: 1678 1899 '@babel/template': 7.25.9 1679 1900 '@babel/types': 7.26.0 1901 + 1902 + '@babel/helpers@7.27.6': 1903 + dependencies: 1904 + '@babel/template': 7.27.2 1905 + '@babel/types': 7.28.0 1680 1906 1681 1907 '@babel/parser@7.26.2': 1682 1908 dependencies: ··· 1686 1912 dependencies: 1687 1913 '@babel/types': 7.27.6 1688 1914 1915 + '@babel/parser@7.28.0': 1916 + dependencies: 1917 + '@babel/types': 7.28.0 1918 + 1689 1919 '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)': 1690 1920 dependencies: 1691 1921 '@babel/core': 7.26.0 1692 1922 '@babel/helper-plugin-utils': 7.25.9 1693 1923 1924 + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.0)': 1925 + dependencies: 1926 + '@babel/core': 7.28.0 1927 + '@babel/helper-plugin-utils': 7.27.1 1928 + 1694 1929 '@babel/template@7.25.9': 1695 1930 dependencies: 1696 1931 '@babel/code-frame': 7.26.2 1697 1932 '@babel/parser': 7.26.2 1698 1933 '@babel/types': 7.26.0 1699 1934 1935 + '@babel/template@7.27.2': 1936 + dependencies: 1937 + '@babel/code-frame': 7.27.1 1938 + '@babel/parser': 7.28.0 1939 + '@babel/types': 7.28.0 1940 + 1700 1941 '@babel/traverse@7.25.9': 1701 1942 dependencies: 1702 1943 '@babel/code-frame': 7.26.2 ··· 1709 1950 transitivePeerDependencies: 1710 1951 - supports-color 1711 1952 1953 + '@babel/traverse@7.28.0': 1954 + dependencies: 1955 + '@babel/code-frame': 7.27.1 1956 + '@babel/generator': 7.28.0 1957 + '@babel/helper-globals': 7.28.0 1958 + '@babel/parser': 7.28.0 1959 + '@babel/template': 7.27.2 1960 + '@babel/types': 7.28.0 1961 + debug: 4.4.1 1962 + transitivePeerDependencies: 1963 + - supports-color 1964 + 1712 1965 '@babel/types@7.26.0': 1713 1966 dependencies: 1714 1967 '@babel/helper-string-parser': 7.25.9 1715 1968 '@babel/helper-validator-identifier': 7.25.9 1716 1969 1717 1970 '@babel/types@7.27.6': 1971 + dependencies: 1972 + '@babel/helper-string-parser': 7.27.1 1973 + '@babel/helper-validator-identifier': 7.27.1 1974 + 1975 + '@babel/types@7.28.0': 1718 1976 dependencies: 1719 1977 '@babel/helper-string-parser': 7.27.1 1720 1978 '@babel/helper-validator-identifier': 7.27.1 ··· 1876 2134 transitivePeerDependencies: 1877 2135 - supports-color 1878 2136 2137 + '@jridgewell/gen-mapping@0.3.12': 2138 + dependencies: 2139 + '@jridgewell/sourcemap-codec': 1.5.0 2140 + '@jridgewell/trace-mapping': 0.3.29 2141 + 1879 2142 '@jridgewell/gen-mapping@0.3.5': 1880 2143 dependencies: 1881 2144 '@jridgewell/set-array': 1.2.1 ··· 1893 2156 '@jridgewell/resolve-uri': 3.1.2 1894 2157 '@jridgewell/sourcemap-codec': 1.5.0 1895 2158 2159 + '@jridgewell/trace-mapping@0.3.29': 2160 + dependencies: 2161 + '@jridgewell/resolve-uri': 3.1.2 2162 + '@jridgewell/sourcemap-codec': 1.5.0 2163 + 1896 2164 '@nodelib/fs.scandir@2.1.5': 1897 2165 dependencies: 1898 2166 '@nodelib/fs.stat': 2.0.5 ··· 1904 2172 dependencies: 1905 2173 '@nodelib/fs.scandir': 2.1.5 1906 2174 fastq: 1.19.1 2175 + 2176 + '@nothing-but/utils@0.17.0': {} 1907 2177 1908 2178 '@polka/url@1.0.0-next.29': {} 1909 2179 ··· 1964 2234 1965 2235 '@rollup/rollup-win32-x64-msvc@4.27.4': 1966 2236 optional: true 2237 + 2238 + '@solid-devtools/debugger@0.28.1(solid-js@1.9.5)': 2239 + dependencies: 2240 + '@nothing-but/utils': 0.17.0 2241 + '@solid-devtools/shared': 0.20.0(solid-js@1.9.5) 2242 + '@solid-primitives/bounds': 0.1.3(solid-js@1.9.5) 2243 + '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.5) 2244 + '@solid-primitives/keyboard': 1.3.3(solid-js@1.9.5) 2245 + '@solid-primitives/rootless': 1.5.2(solid-js@1.9.5) 2246 + '@solid-primitives/scheduled': 1.5.2(solid-js@1.9.5) 2247 + '@solid-primitives/static-store': 0.1.2(solid-js@1.9.5) 2248 + '@solid-primitives/utils': 6.3.2(solid-js@1.9.5) 2249 + solid-js: 1.9.5 2250 + 2251 + '@solid-devtools/shared@0.20.0(solid-js@1.9.5)': 2252 + dependencies: 2253 + '@nothing-but/utils': 0.17.0 2254 + '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.5) 2255 + '@solid-primitives/media': 2.3.3(solid-js@1.9.5) 2256 + '@solid-primitives/refs': 1.1.2(solid-js@1.9.5) 2257 + '@solid-primitives/rootless': 1.5.2(solid-js@1.9.5) 2258 + '@solid-primitives/scheduled': 1.5.2(solid-js@1.9.5) 2259 + '@solid-primitives/static-store': 0.1.2(solid-js@1.9.5) 2260 + '@solid-primitives/styles': 0.1.2(solid-js@1.9.5) 2261 + '@solid-primitives/utils': 6.3.2(solid-js@1.9.5) 2262 + solid-js: 1.9.5 2263 + 2264 + '@solid-primitives/bounds@0.1.3(solid-js@1.9.5)': 2265 + dependencies: 2266 + '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.5) 2267 + '@solid-primitives/resize-observer': 2.1.3(solid-js@1.9.5) 2268 + '@solid-primitives/static-store': 0.1.2(solid-js@1.9.5) 2269 + '@solid-primitives/utils': 6.3.2(solid-js@1.9.5) 2270 + solid-js: 1.9.5 2271 + 2272 + '@solid-primitives/event-listener@2.4.3(solid-js@1.9.5)': 2273 + dependencies: 2274 + '@solid-primitives/utils': 6.3.2(solid-js@1.9.5) 2275 + solid-js: 1.9.5 2276 + 2277 + '@solid-primitives/keyboard@1.3.3(solid-js@1.9.5)': 2278 + dependencies: 2279 + '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.5) 2280 + '@solid-primitives/rootless': 1.5.2(solid-js@1.9.5) 2281 + '@solid-primitives/utils': 6.3.2(solid-js@1.9.5) 2282 + solid-js: 1.9.5 2283 + 2284 + '@solid-primitives/media@2.3.3(solid-js@1.9.5)': 2285 + dependencies: 2286 + '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.5) 2287 + '@solid-primitives/rootless': 1.5.2(solid-js@1.9.5) 2288 + '@solid-primitives/static-store': 0.1.2(solid-js@1.9.5) 2289 + '@solid-primitives/utils': 6.3.2(solid-js@1.9.5) 2290 + solid-js: 1.9.5 2291 + 2292 + '@solid-primitives/refs@1.1.2(solid-js@1.9.5)': 2293 + dependencies: 2294 + '@solid-primitives/utils': 6.3.2(solid-js@1.9.5) 2295 + solid-js: 1.9.5 2296 + 2297 + '@solid-primitives/resize-observer@2.1.3(solid-js@1.9.5)': 2298 + dependencies: 2299 + '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.5) 2300 + '@solid-primitives/rootless': 1.5.2(solid-js@1.9.5) 2301 + '@solid-primitives/static-store': 0.1.2(solid-js@1.9.5) 2302 + '@solid-primitives/utils': 6.3.2(solid-js@1.9.5) 2303 + solid-js: 1.9.5 2304 + 2305 + '@solid-primitives/rootless@1.5.2(solid-js@1.9.5)': 2306 + dependencies: 2307 + '@solid-primitives/utils': 6.3.2(solid-js@1.9.5) 2308 + solid-js: 1.9.5 2309 + 2310 + '@solid-primitives/scheduled@1.5.2(solid-js@1.9.5)': 2311 + dependencies: 2312 + solid-js: 1.9.5 2313 + 2314 + '@solid-primitives/static-store@0.1.2(solid-js@1.9.5)': 2315 + dependencies: 2316 + '@solid-primitives/utils': 6.3.2(solid-js@1.9.5) 2317 + solid-js: 1.9.5 2318 + 2319 + '@solid-primitives/styles@0.1.2(solid-js@1.9.5)': 2320 + dependencies: 2321 + '@solid-primitives/rootless': 1.5.2(solid-js@1.9.5) 2322 + '@solid-primitives/utils': 6.3.2(solid-js@1.9.5) 2323 + solid-js: 1.9.5 2324 + 2325 + '@solid-primitives/utils@6.3.2(solid-js@1.9.5)': 2326 + dependencies: 2327 + solid-js: 1.9.5 1967 2328 1968 2329 '@types/babel__core@7.20.5': 1969 2330 dependencies: ··· 2893 3254 '@polka/url': 1.0.0-next.29 2894 3255 mrmime: 2.0.1 2895 3256 totalist: 3.0.1 3257 + 3258 + solid-devtools@0.34.3(solid-js@1.9.5)(vite@6.0.0(jiti@2.4.2)): 3259 + dependencies: 3260 + '@babel/core': 7.28.0 3261 + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.0) 3262 + '@babel/types': 7.27.6 3263 + '@solid-devtools/debugger': 0.28.1(solid-js@1.9.5) 3264 + '@solid-devtools/shared': 0.20.0(solid-js@1.9.5) 3265 + solid-js: 1.9.5 3266 + optionalDependencies: 3267 + vite: 6.0.0(jiti@2.4.2) 3268 + transitivePeerDependencies: 3269 + - supports-color 2896 3270 2897 3271 solid-js@1.9.5: 2898 3272 dependencies:
+64 -112
webapp/src/App.tsx
··· 1 - import { createSignal, onCleanup, For, type Component } from "solid-js"; 1 + import { createSignal, onCleanup, For, type Component, Signal } from "solid-js"; 2 2 3 3 import type {} from "@atcute/bluesky"; 4 4 import type {} from "@atcute/atproto"; 5 - import { isDid, isHandle } from "@atcute/lexicons/syntax"; 6 - import { XrpcHandleResolver } from "@atcute/identity-resolver"; 7 - import { Notification } from "./types.js"; 5 + import { ConnectionStatus, Notification } from "./types.js"; 8 6 import { ActivityItem } from "./ActivityItem.jsx"; 9 - 10 - const handleResolver = new XrpcHandleResolver({ 11 - serviceUrl: "https://public.api.bsky.app", 12 - }); 7 + import { connect as connectService } from "./ws.ts"; 8 + import { Accessor } from "solid-js/types/server/reactive.js"; 13 9 14 - const App: Component = () => { 10 + const Wrapped: Component = () => { 15 11 const [actorId, setActorId] = createSignal<string>(""); 16 12 const [serviceDomain, setWsUrl] = createSignal<string>("likes.gaze.systems"); 17 - const [isConnected, setIsConnected] = createSignal<boolean>(false); 18 13 const [items, setItems] = createSignal<Notification[]>([]); 19 - const [connectionStatus, setConnectionStatus] = createSignal< 20 - "disconnected" | "connecting..." | "connected" | "error" 21 - >("disconnected"); 14 + const [connectionStatus, setConnectionStatus] = 15 + createSignal<ConnectionStatus>("disconnected"); 22 16 const [error, setError] = createSignal<string | null>(null); 23 - 24 - let ws: WebSocket | null = null; 25 - 26 - const connectWebSocket = async () => { 27 - const didOrHandle = actorId().trim(); 28 - const host = serviceDomain().trim(); 29 - 30 - setError(null); 31 - setConnectionStatus("connecting..."); 32 - 33 - let did: string; 34 - if (!didOrHandle) { 35 - setConnectionStatus("error"); 36 - setError("please enter a DID or a handle"); 37 - return; 38 - } else if (isHandle(didOrHandle)) { 39 - try { 40 - did = await handleResolver.resolve(didOrHandle); 41 - } catch (error) { 42 - setConnectionStatus("error"); 43 - setError(`can't resolve handle: ${error}`); 44 - return; 45 - } 46 - } else if (isDid(didOrHandle)) { 47 - did = didOrHandle; 48 - } else { 49 - setConnectionStatus("error"); 50 - setError("inputted DID / handle is not valid"); 51 - return; 52 - } 53 - 54 - if (!host) { 55 - setError("please enter service host"); 56 - setConnectionStatus("error"); 57 - return; 58 - } 59 - 60 - // Close existing connection if any 61 - if (ws) { 62 - ws.close(); 63 - } 64 - 65 - let proto = "wss"; 66 - const domain = host.split(":").at(0) ?? ""; 67 - if (["localhost", "0.0.0.0", "127.0.0.1"].some((v) => v === domain)) { 68 - proto = "ws"; 69 - } 17 + const [ws, setWs] = createSignal<WebSocket | null>(null); 70 18 71 - const url = `${proto}://${host}/subscribe/${did}`; 19 + const connect = async () => { 20 + // close existing connection if any 21 + ws()?.close(); 22 + setWs( 23 + (await connectService({ 24 + actorId, 25 + pushNotification: (item) => setItems((prev) => [item, ...prev]), 26 + serviceDomain, 27 + setConnectionStatus, 28 + setError, 29 + })) ?? null, 30 + ); 31 + }; 72 32 73 - try { 74 - ws = new WebSocket(url); 33 + const disconnect = (): void => { 34 + setConnectionStatus("disconnecting..."); 35 + ws()?.close(); 36 + setWs(null); 37 + }; 75 38 76 - ws.onopen = () => { 77 - setIsConnected(true); 78 - setConnectionStatus("connected"); 79 - setError(null); 80 - console.log("WebSocket connected to:", url); 81 - }; 39 + onCleanup(disconnect); 82 40 83 - ws.onmessage = (event: MessageEvent) => { 84 - try { 85 - const data: Notification = JSON.parse(event.data); 86 - setItems((prev) => [data, ...prev]); // add new items to the top 87 - } catch (error) { 88 - console.error("Error parsing JSON:", error); 89 - } 90 - }; 41 + const props: AppProps = { 42 + actorIdSignal: [actorId, setActorId], 43 + serviceDomainSignal: [serviceDomain, setWsUrl], 44 + itemsSignal: [items, setItems], 45 + connectionStatus, 46 + error, 47 + connect, 48 + disconnect, 49 + }; 91 50 92 - ws.onclose = () => { 93 - setIsConnected(false); 94 - setConnectionStatus("disconnected"); 95 - console.log("WebSocket disconnected"); 96 - }; 51 + return <App {...props} />; 52 + }; 53 + export default Wrapped; 97 54 98 - ws.onerror = (error: Event) => { 99 - setConnectionStatus("error"); 100 - setError(`connection failed: ${error}`); 101 - console.error("WebSocket error:", error); 102 - }; 103 - } catch (error) { 104 - setConnectionStatus("error"); 105 - setError(`failed to create connection: ${error}`); 106 - console.error("Failed to create WebSocket:", error); 107 - } 108 - }; 55 + export interface AppProps { 56 + actorIdSignal: Signal<string>; 57 + serviceDomainSignal: Signal<string>; 58 + itemsSignal: Signal<Notification[]>; 59 + connectionStatus: Accessor<ConnectionStatus>; 60 + error: Accessor<string | null>; 61 + connect: () => void; 62 + disconnect: () => void; 63 + } 109 64 110 - const disconnect = (): void => { 111 - if (ws) { 112 - ws.close(); 113 - ws = null; 114 - } 115 - }; 65 + export const App: Component<AppProps> = (props) => { 66 + const [actorId, setActorId] = props.actorIdSignal; 67 + const [serviceDomain, setWsUrl] = props.serviceDomainSignal; 68 + const [items, setItems] = props.itemsSignal; 69 + const { disconnect, connect, error, connectionStatus } = props; 116 70 117 71 const clearItems = (): void => { 118 72 setItems([]); 119 73 }; 120 74 121 - onCleanup(() => { 122 - if (ws) { 123 - ws.close(); 124 - } 125 - }); 75 + const isConnected = () => { 76 + return ( 77 + connectionStatus() == "connecting..." || connectionStatus() == "connected" 78 + ); 79 + }; 126 80 127 81 return ( 128 - <div max-w-4xl mx-auto p-6 bg-gray-50 min-h-screen> 82 + <div max-w-4xl mx-auto p-4 bg-gray-50 min-h-screen> 129 83 <h1 border="l-16 blue" font-bold text="3xl gray-800" pl-2 mb-6> 130 84 monitor bluesky repost likes 131 85 </h1> ··· 139 93 onInput={(e) => setWsUrl((e.target as HTMLInputElement).value)} 140 94 placeholder="enter service host (e.g., likes.gaze.systems)" 141 95 class="flex-1 px-4 py-2 border border-gray-300 rounded-none focus:(outline-none ring-2 ring-purple-500) bg-white" 142 - disabled={isConnected() || connectionStatus() == "connecting..."} 96 + disabled={isConnected()} 143 97 /> 144 98 </div> 145 99 <div flex gap-2 mb-2> ··· 149 103 onInput={(e) => setActorId((e.target as HTMLInputElement).value)} 150 104 onKeyPress={(e) => { 151 105 if (!isConnected() && e.key == "Enter") { 152 - connectWebSocket(); 106 + connect(); 153 107 e.preventDefault(); 154 108 } 155 109 }} 156 110 placeholder="enter handle or DID" 157 111 class="flex-1 px-4 py-2 border border-gray-300 rounded-none focus:(outline-none ring-2 ring-blue-500) bg-white" 158 - disabled={isConnected() || connectionStatus() == "connecting..."} 112 + disabled={isConnected()} 159 113 /> 160 114 <button 161 - onClick={() => (isConnected() ? disconnect() : connectWebSocket())} 115 + onClick={() => (isConnected() ? disconnect() : connect())} 162 116 class={`px-6 py-2 rounded-none font-medium transition-colors ${ 163 117 isConnected() 164 118 ? "bg-red-500 hover:bg-red-600 text-white" 165 119 : "bg-blue-500 hover:bg-blue-600 text-white" 166 120 }`} 167 121 > 168 - {isConnected() ? "Disconnect" : "Connect"} 122 + {isConnected() ? "disconnect" : "connect"} 169 123 </button> 170 124 </div> 171 125 ··· 260 214 </div> 261 215 ); 262 216 }; 263 - 264 - export default App;
+3 -2
webapp/src/index.tsx
··· 1 1 /* @refresh reload */ 2 2 import { render } from "solid-js/web"; 3 + import "solid-devtools"; 4 + 5 + import App from "./App.tsx"; 3 6 4 7 import "./index.css"; 5 - import App from "./App"; 6 - 7 8 import "virtual:uno.css"; 8 9 9 10 const root = document.getElementById("root");
+11
webapp/src/lib.d.ts
··· 1 + import { Component } from "solid-js"; 2 + import { ConnectionStatus, Notification, NotificationActor } from "./types.ts"; 3 + import { Callbacks as WsCallbacks, connect } from "./ws.ts"; 4 + import { AppProps } from "./App.tsx"; 5 + 6 + export const App: Component<AppProps>; 7 + export const ConnectionStatus: ConnectionStatus; 8 + export const Notification: Notification; 9 + export const NotificationActor: NotificationActor; 10 + export const WebsocketCallbacks: WsCallbacks; 11 + export const connectService: typeof connect;
+13
webapp/src/lib.ts
··· 1 + import "./index.css"; 2 + import "virtual:uno.css"; 3 + 4 + export { App, type AppProps } from "./App.tsx"; 5 + export type { 6 + Notification, 7 + NotificationActor, 8 + ConnectionStatus, 9 + } from "./types.ts"; 10 + export { 11 + type Callbacks as WebsocketCallbacks, 12 + connect as connectService, 13 + } from "./ws.ts";
+7
webapp/src/types.ts
··· 13 13 did: Did; 14 14 profile?: ProfileViewDetailed; 15 15 } 16 + 17 + export type ConnectionStatus = 18 + | "disconnected" 19 + | "disconnecting..." 20 + | "connecting..." 21 + | "connected" 22 + | "error";
+97
webapp/src/ws.ts
··· 1 + import { XrpcHandleResolver } from "@atcute/identity-resolver"; 2 + import { isDid, isHandle } from "@atcute/lexicons/syntax"; 3 + import { ConnectionStatus, Notification } from "./types.ts"; 4 + 5 + export interface Callbacks { 6 + setError: (error: string | null) => void; 7 + setConnectionStatus: (status: ConnectionStatus) => void; 8 + pushNotification: (item: Notification) => void; 9 + actorId: () => string; 10 + serviceDomain: () => string; 11 + } 12 + 13 + const handleResolver = new XrpcHandleResolver({ 14 + serviceUrl: "https://public.api.bsky.app", 15 + }); 16 + 17 + export const connect = async (cb: Callbacks) => { 18 + const didOrHandle = cb.actorId().trim(); 19 + const host = cb.serviceDomain().trim(); 20 + 21 + cb.setError(null); 22 + cb.setConnectionStatus("connecting..."); 23 + 24 + let did: string; 25 + if (!didOrHandle) { 26 + cb.setConnectionStatus("error"); 27 + cb.setError("please enter a DID or a handle"); 28 + return; 29 + } else if (isHandle(didOrHandle)) { 30 + try { 31 + did = await handleResolver.resolve(didOrHandle); 32 + } catch (error) { 33 + cb.setConnectionStatus("error"); 34 + cb.setError(`can't resolve handle: ${error}`); 35 + return; 36 + } 37 + } else if (isDid(didOrHandle)) { 38 + did = didOrHandle; 39 + } else { 40 + cb.setConnectionStatus("error"); 41 + cb.setError("inputted DID / handle is not valid"); 42 + return; 43 + } 44 + 45 + if (!host) { 46 + cb.setError("please enter service host"); 47 + cb.setConnectionStatus("error"); 48 + return; 49 + } 50 + 51 + let proto = "wss"; 52 + const domain = host.split(":").at(0) ?? ""; 53 + if (["localhost", "0.0.0.0", "127.0.0.1"].some((v) => v === domain)) { 54 + proto = "ws"; 55 + } 56 + 57 + const url = `${proto}://${host}/subscribe/${did}`; 58 + 59 + try { 60 + let ws = new WebSocket(url); 61 + 62 + ws.onopen = () => { 63 + cb.setConnectionStatus("connected"); 64 + cb.setError(null); 65 + console.log("WebSocket connected to:", url); 66 + }; 67 + 68 + ws.onmessage = (event: MessageEvent) => { 69 + try { 70 + cb.pushNotification(JSON.parse(event.data)); 71 + } catch (error) { 72 + console.error("Error parsing JSON:", error); 73 + } 74 + }; 75 + 76 + ws.onclose = () => { 77 + cb.setConnectionStatus("disconnected"); 78 + console.log("WebSocket disconnected"); 79 + }; 80 + 81 + ws.onerror = (error: Event) => { 82 + cb.setConnectionStatus("error"); 83 + cb.setError(`connection failed: ${error}`); 84 + console.error("WebSocket error:", error); 85 + }; 86 + 87 + return ws; 88 + } catch (error) { 89 + cb.setConnectionStatus("error"); 90 + cb.setError(`failed to create connection: ${error}`); 91 + console.error("Failed to create WebSocket:", error); 92 + } 93 + 94 + return; 95 + }; 96 + 97 + export default connect;
+1
webapp/tsconfig.json
··· 5 5 "module": "NodeNext", 6 6 "moduleResolution": "nodenext", 7 7 "allowSyntheticDefaultImports": true, 8 + "allowImportingTsExtensions": true, 8 9 "esModuleInterop": true, 9 10 "jsx": "preserve", 10 11 "jsxImportSource": "solid-js",
+42
webapp/vite.config.lib.ts
··· 1 + import { defineConfig } from "vite"; 2 + import solidPlugin from "vite-plugin-solid"; 3 + 4 + import UnoCSS from "unocss/vite"; 5 + import { 6 + presetAttributify, 7 + presetWind4, 8 + transformerAttributifyJsx, 9 + transformerVariantGroup, 10 + transformerDirectives, 11 + } from "unocss"; 12 + 13 + export default defineConfig({ 14 + plugins: [ 15 + UnoCSS({ 16 + presets: [presetWind4(), presetAttributify()], 17 + transformers: [ 18 + transformerVariantGroup(), 19 + transformerDirectives(), 20 + transformerAttributifyJsx(), 21 + ], 22 + }), 23 + solidPlugin(), 24 + ], 25 + build: { 26 + target: "esnext", 27 + lib: { 28 + entry: "./src/lib.ts", 29 + name: "bsky-repost-likes-monitor", 30 + formats: ["es"], 31 + fileName: "index", 32 + }, 33 + rollupOptions: { 34 + external: ["solid-js", "solid-js/web"], 35 + output: { 36 + globals: { 37 + "solid-js": "SolidJS", 38 + }, 39 + }, 40 + }, 41 + }, 42 + });
+2
webapp/vite.config.ts
··· 1 1 import { defineConfig } from "vite"; 2 2 import solidPlugin from "vite-plugin-solid"; 3 + import devtools from "solid-devtools/vite"; 3 4 4 5 import UnoCSS from "unocss/vite"; 5 6 import { ··· 20 21 transformerAttributifyJsx(), 21 22 ], 22 23 }), 24 + devtools({ autoname: true }), 23 25 solidPlugin(), 24 26 ], 25 27 server: {