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
+16 -1
webapp/package.json
···
{
-
"name": "vite-template-solid",
+
"name": "bsky-repost-likes-monitor",
"version": "0.0.0",
"description": "",
"type": "module",
···
"start": "vite",
"dev": "vite",
"build": "vite build",
+
"build:lib": "vite build --config vite.config.lib.ts",
"serve": "vite preview"
},
"license": "MIT",
+
"main": "./dist/index.js",
+
"module": "./dist/index.js",
+
"types": "./dist/index.d.ts",
+
"exports": {
+
".": {
+
"import": "./dist/index.js",
+
"types": "./dist/index.d.ts"
+
},
+
"./style.css": "./dist/index.css"
+
},
+
"files": [
+
"dist"
+
],
"devDependencies": {
"@eslint/css": "^0.8.1",
"@eslint/js": "^9.28.0",
···
"eslint-plugin-solid": "^0.14.5",
"globals": "^16.2.0",
"prettier": "3.5.3",
+
"solid-devtools": "^0.34.3",
"typescript": "^5.7.2",
"typescript-eslint": "^8.33.1",
"unocss": "^66.1.4",
+374
webapp/pnpm-lock.yaml
···
prettier:
specifier: 3.5.3
version: 3.5.3
+
solid-devtools:
+
specifier: ^0.34.3
+
version: 0.34.3(solid-js@1.9.5)(vite@6.0.0(jiti@2.4.2))
typescript:
specifier: ^5.7.2
version: 5.7.2
···
resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
engines: {node: '>=6.9.0'}
+
'@babel/code-frame@7.27.1':
+
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
+
engines: {node: '>=6.9.0'}
+
'@babel/compat-data@7.26.2':
resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==}
engines: {node: '>=6.9.0'}
+
'@babel/compat-data@7.28.0':
+
resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==}
+
engines: {node: '>=6.9.0'}
+
'@babel/core@7.26.0':
resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==}
engines: {node: '>=6.9.0'}
+
'@babel/core@7.28.0':
+
resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==}
+
engines: {node: '>=6.9.0'}
+
'@babel/generator@7.26.2':
resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==}
+
engines: {node: '>=6.9.0'}
+
+
'@babel/generator@7.28.0':
+
resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==}
engines: {node: '>=6.9.0'}
'@babel/helper-compilation-targets@7.25.9':
resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==}
engines: {node: '>=6.9.0'}
+
'@babel/helper-compilation-targets@7.27.2':
+
resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
+
engines: {node: '>=6.9.0'}
+
+
'@babel/helper-globals@7.28.0':
+
resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+
engines: {node: '>=6.9.0'}
+
'@babel/helper-module-imports@7.18.6':
resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==}
engines: {node: '>=6.9.0'}
···
resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==}
engines: {node: '>=6.9.0'}
+
'@babel/helper-module-imports@7.27.1':
+
resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
+
engines: {node: '>=6.9.0'}
+
'@babel/helper-module-transforms@7.26.0':
resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
+
'@babel/helper-module-transforms@7.27.3':
+
resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==}
+
engines: {node: '>=6.9.0'}
+
peerDependencies:
+
'@babel/core': ^7.0.0
+
'@babel/helper-plugin-utils@7.25.9':
resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==}
+
engines: {node: '>=6.9.0'}
+
+
'@babel/helper-plugin-utils@7.27.1':
+
resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
engines: {node: '>=6.9.0'}
'@babel/helper-string-parser@7.25.9':
···
resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==}
engines: {node: '>=6.9.0'}
+
'@babel/helper-validator-option@7.27.1':
+
resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
+
engines: {node: '>=6.9.0'}
+
'@babel/helpers@7.26.0':
resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==}
engines: {node: '>=6.9.0'}
+
'@babel/helpers@7.27.6':
+
resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==}
+
engines: {node: '>=6.9.0'}
+
'@babel/parser@7.26.2':
resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==}
engines: {node: '>=6.0.0'}
···
'@babel/parser@7.27.5':
resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==}
+
engines: {node: '>=6.0.0'}
+
hasBin: true
+
+
'@babel/parser@7.28.0':
+
resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==}
engines: {node: '>=6.0.0'}
hasBin: true
'@babel/plugin-syntax-jsx@7.25.9':
resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==}
+
engines: {node: '>=6.9.0'}
+
peerDependencies:
+
'@babel/core': ^7.0.0-0
+
+
'@babel/plugin-syntax-typescript@7.27.1':
+
resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
···
resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==}
engines: {node: '>=6.9.0'}
+
'@babel/template@7.27.2':
+
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
+
engines: {node: '>=6.9.0'}
+
'@babel/traverse@7.25.9':
resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==}
engines: {node: '>=6.9.0'}
+
'@babel/traverse@7.28.0':
+
resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==}
+
engines: {node: '>=6.9.0'}
+
'@babel/types@7.26.0':
resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==}
engines: {node: '>=6.9.0'}
'@babel/types@7.27.6':
resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==}
+
engines: {node: '>=6.9.0'}
+
+
'@babel/types@7.28.0':
+
resolution: {integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==}
engines: {node: '>=6.9.0'}
'@badrap/valita@0.4.5':
···
'@iconify/utils@2.3.0':
resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==}
+
'@jridgewell/gen-mapping@0.3.12':
+
resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==}
+
'@jridgewell/gen-mapping@0.3.5':
resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
engines: {node: '>=6.0.0'}
···
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
+
'@jridgewell/trace-mapping@0.3.29':
+
resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==}
+
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
···
'@nodelib/fs.walk@1.2.8':
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
+
+
'@nothing-but/utils@0.17.0':
+
resolution: {integrity: sha512-TuCHcHLOqDL0SnaAxACfuRHBNRgNJcNn9X0GiH5H3YSDBVquCr3qEIG3FOQAuMyZCbu9w8nk2CHhOsn7IvhIwQ==}
'@polka/url@1.0.0-next.29':
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
···
cpu: [x64]
os: [win32]
+
'@solid-devtools/debugger@0.28.1':
+
resolution: {integrity: sha512-6qIUI6VYkXoRnL8oF5bvh2KgH71qlJ18hNw/mwSyY6v48eb80ZR48/5PDXufUa3q+MBSuYa1uqTMwLewpay9eg==}
+
peerDependencies:
+
solid-js: ^1.9.0
+
+
'@solid-devtools/shared@0.20.0':
+
resolution: {integrity: sha512-o5TACmUOQsxpzpOKCjbQqGk8wL8PMi+frXG9WNu4Lh3PQVUB6hs95Kl/S8xc++zwcMguUKZJn8h5URUiMOca6Q==}
+
peerDependencies:
+
solid-js: ^1.9.0
+
+
'@solid-primitives/bounds@0.1.3':
+
resolution: {integrity: sha512-UbiyKMdSPmtijcEDnYLQL3zzaejpwWDAJJ4Gt5P0hgVs6A72piov0GyNw7V2SroH7NZFwxlYS22YmOr8A5xc1Q==}
+
peerDependencies:
+
solid-js: ^1.6.12
+
+
'@solid-primitives/event-listener@2.4.3':
+
resolution: {integrity: sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg==}
+
peerDependencies:
+
solid-js: ^1.6.12
+
+
'@solid-primitives/keyboard@1.3.3':
+
resolution: {integrity: sha512-9dQHTTgLBqyAI7aavtO+HnpTVJgWQA1ghBSrmLtMu1SMxLPDuLfuNr+Tk5udb4AL4Ojg7h9JrKOGEEDqsJXWJA==}
+
peerDependencies:
+
solid-js: ^1.6.12
+
+
'@solid-primitives/media@2.3.3':
+
resolution: {integrity: sha512-hQ4hLOGvfbugQi5Eu1BFWAIJGIAzztq9x0h02xgBGl2l0Jaa3h7tg6bz5tV1NSuNYVGio4rPoa7zVQQLkkx9dA==}
+
peerDependencies:
+
solid-js: ^1.6.12
+
+
'@solid-primitives/refs@1.1.2':
+
resolution: {integrity: sha512-K7tf2thy7L+YJjdqXspXOg5xvNEOH8tgEWsp0+1mQk3obHBRD6hEjYZk7p7FlJphSZImS35je3UfmWuD7MhDfg==}
+
peerDependencies:
+
solid-js: ^1.6.12
+
+
'@solid-primitives/resize-observer@2.1.3':
+
resolution: {integrity: sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ==}
+
peerDependencies:
+
solid-js: ^1.6.12
+
+
'@solid-primitives/rootless@1.5.2':
+
resolution: {integrity: sha512-9HULb0QAzL2r47CCad0M+NKFtQ+LrGGNHZfteX/ThdGvKIg2o2GYhBooZubTCd/RTu2l2+Nw4s+dEfiDGvdrrQ==}
+
peerDependencies:
+
solid-js: ^1.6.12
+
+
'@solid-primitives/scheduled@1.5.2':
+
resolution: {integrity: sha512-/j2igE0xyNaHhj6kMfcUQn5rAVSTLbAX+CDEBm25hSNBmNiHLu2lM7Usj2kJJ5j36D67bE8wR1hBNA8hjtvsQA==}
+
peerDependencies:
+
solid-js: ^1.6.12
+
+
'@solid-primitives/static-store@0.1.2':
+
resolution: {integrity: sha512-ReK+5O38lJ7fT+L6mUFvUr6igFwHBESZF+2Ug842s7fvlVeBdIVEdTCErygff6w7uR6+jrr7J8jQo+cYrEq4Iw==}
+
peerDependencies:
+
solid-js: ^1.6.12
+
+
'@solid-primitives/styles@0.1.2':
+
resolution: {integrity: sha512-7iX5K+J5b1PRrbgw3Ki92uvU2LgQ0Kd/QMsrAZxDg5dpUBwMyTijZkA3bbs1ikZsT1oQhS41bTyKbjrXeU0Awg==}
+
peerDependencies:
+
solid-js: ^1.6.12
+
+
'@solid-primitives/utils@6.3.2':
+
resolution: {integrity: sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ==}
+
peerDependencies:
+
solid-js: ^1.6.12
+
'@types/babel__core@7.20.5':
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
···
resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==}
engines: {node: '>=18'}
+
solid-devtools@0.34.3:
+
resolution: {integrity: sha512-ZQua959n+Zu3sLbm9g0IRjYUb1YYlYbu83PWLRoKbSsq0a3ItQNhnS2OBU7rQNmOKZiMexNo9Z3izas9BcOKDg==}
+
peerDependencies:
+
solid-js: ^1.9.0
+
vite: ^2.2.3 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0
+
peerDependenciesMeta:
+
vite:
+
optional: true
+
solid-js@1.9.5:
resolution: {integrity: sha512-ogI3DaFcyn6UhYhrgcyRAMbu/buBJitYQASZz5WzfQVPP10RD2AbCoRZ517psnezrasyCbWzIxZ6kVqet768xw==}
···
js-tokens: 4.0.0
picocolors: 1.1.1
+
'@babel/code-frame@7.27.1':
+
dependencies:
+
'@babel/helper-validator-identifier': 7.27.1
+
js-tokens: 4.0.0
+
picocolors: 1.1.1
+
'@babel/compat-data@7.26.2': {}
+
+
'@babel/compat-data@7.28.0': {}
'@babel/core@7.26.0':
dependencies:
···
transitivePeerDependencies:
- supports-color
+
'@babel/core@7.28.0':
+
dependencies:
+
'@ampproject/remapping': 2.3.0
+
'@babel/code-frame': 7.27.1
+
'@babel/generator': 7.28.0
+
'@babel/helper-compilation-targets': 7.27.2
+
'@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0)
+
'@babel/helpers': 7.27.6
+
'@babel/parser': 7.28.0
+
'@babel/template': 7.27.2
+
'@babel/traverse': 7.28.0
+
'@babel/types': 7.28.0
+
convert-source-map: 2.0.0
+
debug: 4.4.1
+
gensync: 1.0.0-beta.2
+
json5: 2.2.3
+
semver: 6.3.1
+
transitivePeerDependencies:
+
- supports-color
+
'@babel/generator@7.26.2':
dependencies:
'@babel/parser': 7.26.2
···
'@jridgewell/trace-mapping': 0.3.25
jsesc: 3.0.2
+
'@babel/generator@7.28.0':
+
dependencies:
+
'@babel/parser': 7.28.0
+
'@babel/types': 7.28.0
+
'@jridgewell/gen-mapping': 0.3.12
+
'@jridgewell/trace-mapping': 0.3.29
+
jsesc: 3.0.2
+
'@babel/helper-compilation-targets@7.25.9':
dependencies:
'@babel/compat-data': 7.26.2
···
lru-cache: 5.1.1
semver: 6.3.1
+
'@babel/helper-compilation-targets@7.27.2':
+
dependencies:
+
'@babel/compat-data': 7.28.0
+
'@babel/helper-validator-option': 7.27.1
+
browserslist: 4.24.2
+
lru-cache: 5.1.1
+
semver: 6.3.1
+
+
'@babel/helper-globals@7.28.0': {}
+
'@babel/helper-module-imports@7.18.6':
dependencies:
'@babel/types': 7.26.0
···
transitivePeerDependencies:
- supports-color
+
'@babel/helper-module-imports@7.27.1':
+
dependencies:
+
'@babel/traverse': 7.28.0
+
'@babel/types': 7.28.0
+
transitivePeerDependencies:
+
- supports-color
+
'@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)':
dependencies:
'@babel/core': 7.26.0
···
transitivePeerDependencies:
- supports-color
+
'@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)':
+
dependencies:
+
'@babel/core': 7.28.0
+
'@babel/helper-module-imports': 7.27.1
+
'@babel/helper-validator-identifier': 7.27.1
+
'@babel/traverse': 7.28.0
+
transitivePeerDependencies:
+
- supports-color
+
'@babel/helper-plugin-utils@7.25.9': {}
+
+
'@babel/helper-plugin-utils@7.27.1': {}
'@babel/helper-string-parser@7.25.9': {}
···
'@babel/helper-validator-option@7.25.9': {}
+
'@babel/helper-validator-option@7.27.1': {}
+
'@babel/helpers@7.26.0':
dependencies:
'@babel/template': 7.25.9
'@babel/types': 7.26.0
+
+
'@babel/helpers@7.27.6':
+
dependencies:
+
'@babel/template': 7.27.2
+
'@babel/types': 7.28.0
'@babel/parser@7.26.2':
dependencies:
···
dependencies:
'@babel/types': 7.27.6
+
'@babel/parser@7.28.0':
+
dependencies:
+
'@babel/types': 7.28.0
+
'@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)':
dependencies:
'@babel/core': 7.26.0
'@babel/helper-plugin-utils': 7.25.9
+
'@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.0)':
+
dependencies:
+
'@babel/core': 7.28.0
+
'@babel/helper-plugin-utils': 7.27.1
+
'@babel/template@7.25.9':
dependencies:
'@babel/code-frame': 7.26.2
'@babel/parser': 7.26.2
'@babel/types': 7.26.0
+
'@babel/template@7.27.2':
+
dependencies:
+
'@babel/code-frame': 7.27.1
+
'@babel/parser': 7.28.0
+
'@babel/types': 7.28.0
+
'@babel/traverse@7.25.9':
dependencies:
'@babel/code-frame': 7.26.2
···
transitivePeerDependencies:
- supports-color
+
'@babel/traverse@7.28.0':
+
dependencies:
+
'@babel/code-frame': 7.27.1
+
'@babel/generator': 7.28.0
+
'@babel/helper-globals': 7.28.0
+
'@babel/parser': 7.28.0
+
'@babel/template': 7.27.2
+
'@babel/types': 7.28.0
+
debug: 4.4.1
+
transitivePeerDependencies:
+
- supports-color
+
'@babel/types@7.26.0':
dependencies:
'@babel/helper-string-parser': 7.25.9
'@babel/helper-validator-identifier': 7.25.9
'@babel/types@7.27.6':
+
dependencies:
+
'@babel/helper-string-parser': 7.27.1
+
'@babel/helper-validator-identifier': 7.27.1
+
+
'@babel/types@7.28.0':
dependencies:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.27.1
···
transitivePeerDependencies:
- supports-color
+
'@jridgewell/gen-mapping@0.3.12':
+
dependencies:
+
'@jridgewell/sourcemap-codec': 1.5.0
+
'@jridgewell/trace-mapping': 0.3.29
+
'@jridgewell/gen-mapping@0.3.5':
dependencies:
'@jridgewell/set-array': 1.2.1
···
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
+
'@jridgewell/trace-mapping@0.3.29':
+
dependencies:
+
'@jridgewell/resolve-uri': 3.1.2
+
'@jridgewell/sourcemap-codec': 1.5.0
+
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
···
dependencies:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.19.1
+
+
'@nothing-but/utils@0.17.0': {}
'@polka/url@1.0.0-next.29': {}
···
'@rollup/rollup-win32-x64-msvc@4.27.4':
optional: true
+
+
'@solid-devtools/debugger@0.28.1(solid-js@1.9.5)':
+
dependencies:
+
'@nothing-but/utils': 0.17.0
+
'@solid-devtools/shared': 0.20.0(solid-js@1.9.5)
+
'@solid-primitives/bounds': 0.1.3(solid-js@1.9.5)
+
'@solid-primitives/event-listener': 2.4.3(solid-js@1.9.5)
+
'@solid-primitives/keyboard': 1.3.3(solid-js@1.9.5)
+
'@solid-primitives/rootless': 1.5.2(solid-js@1.9.5)
+
'@solid-primitives/scheduled': 1.5.2(solid-js@1.9.5)
+
'@solid-primitives/static-store': 0.1.2(solid-js@1.9.5)
+
'@solid-primitives/utils': 6.3.2(solid-js@1.9.5)
+
solid-js: 1.9.5
+
+
'@solid-devtools/shared@0.20.0(solid-js@1.9.5)':
+
dependencies:
+
'@nothing-but/utils': 0.17.0
+
'@solid-primitives/event-listener': 2.4.3(solid-js@1.9.5)
+
'@solid-primitives/media': 2.3.3(solid-js@1.9.5)
+
'@solid-primitives/refs': 1.1.2(solid-js@1.9.5)
+
'@solid-primitives/rootless': 1.5.2(solid-js@1.9.5)
+
'@solid-primitives/scheduled': 1.5.2(solid-js@1.9.5)
+
'@solid-primitives/static-store': 0.1.2(solid-js@1.9.5)
+
'@solid-primitives/styles': 0.1.2(solid-js@1.9.5)
+
'@solid-primitives/utils': 6.3.2(solid-js@1.9.5)
+
solid-js: 1.9.5
+
+
'@solid-primitives/bounds@0.1.3(solid-js@1.9.5)':
+
dependencies:
+
'@solid-primitives/event-listener': 2.4.3(solid-js@1.9.5)
+
'@solid-primitives/resize-observer': 2.1.3(solid-js@1.9.5)
+
'@solid-primitives/static-store': 0.1.2(solid-js@1.9.5)
+
'@solid-primitives/utils': 6.3.2(solid-js@1.9.5)
+
solid-js: 1.9.5
+
+
'@solid-primitives/event-listener@2.4.3(solid-js@1.9.5)':
+
dependencies:
+
'@solid-primitives/utils': 6.3.2(solid-js@1.9.5)
+
solid-js: 1.9.5
+
+
'@solid-primitives/keyboard@1.3.3(solid-js@1.9.5)':
+
dependencies:
+
'@solid-primitives/event-listener': 2.4.3(solid-js@1.9.5)
+
'@solid-primitives/rootless': 1.5.2(solid-js@1.9.5)
+
'@solid-primitives/utils': 6.3.2(solid-js@1.9.5)
+
solid-js: 1.9.5
+
+
'@solid-primitives/media@2.3.3(solid-js@1.9.5)':
+
dependencies:
+
'@solid-primitives/event-listener': 2.4.3(solid-js@1.9.5)
+
'@solid-primitives/rootless': 1.5.2(solid-js@1.9.5)
+
'@solid-primitives/static-store': 0.1.2(solid-js@1.9.5)
+
'@solid-primitives/utils': 6.3.2(solid-js@1.9.5)
+
solid-js: 1.9.5
+
+
'@solid-primitives/refs@1.1.2(solid-js@1.9.5)':
+
dependencies:
+
'@solid-primitives/utils': 6.3.2(solid-js@1.9.5)
+
solid-js: 1.9.5
+
+
'@solid-primitives/resize-observer@2.1.3(solid-js@1.9.5)':
+
dependencies:
+
'@solid-primitives/event-listener': 2.4.3(solid-js@1.9.5)
+
'@solid-primitives/rootless': 1.5.2(solid-js@1.9.5)
+
'@solid-primitives/static-store': 0.1.2(solid-js@1.9.5)
+
'@solid-primitives/utils': 6.3.2(solid-js@1.9.5)
+
solid-js: 1.9.5
+
+
'@solid-primitives/rootless@1.5.2(solid-js@1.9.5)':
+
dependencies:
+
'@solid-primitives/utils': 6.3.2(solid-js@1.9.5)
+
solid-js: 1.9.5
+
+
'@solid-primitives/scheduled@1.5.2(solid-js@1.9.5)':
+
dependencies:
+
solid-js: 1.9.5
+
+
'@solid-primitives/static-store@0.1.2(solid-js@1.9.5)':
+
dependencies:
+
'@solid-primitives/utils': 6.3.2(solid-js@1.9.5)
+
solid-js: 1.9.5
+
+
'@solid-primitives/styles@0.1.2(solid-js@1.9.5)':
+
dependencies:
+
'@solid-primitives/rootless': 1.5.2(solid-js@1.9.5)
+
'@solid-primitives/utils': 6.3.2(solid-js@1.9.5)
+
solid-js: 1.9.5
+
+
'@solid-primitives/utils@6.3.2(solid-js@1.9.5)':
+
dependencies:
+
solid-js: 1.9.5
'@types/babel__core@7.20.5':
dependencies:
···
'@polka/url': 1.0.0-next.29
mrmime: 2.0.1
totalist: 3.0.1
+
+
solid-devtools@0.34.3(solid-js@1.9.5)(vite@6.0.0(jiti@2.4.2)):
+
dependencies:
+
'@babel/core': 7.28.0
+
'@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.0)
+
'@babel/types': 7.27.6
+
'@solid-devtools/debugger': 0.28.1(solid-js@1.9.5)
+
'@solid-devtools/shared': 0.20.0(solid-js@1.9.5)
+
solid-js: 1.9.5
+
optionalDependencies:
+
vite: 6.0.0(jiti@2.4.2)
+
transitivePeerDependencies:
+
- supports-color
solid-js@1.9.5:
dependencies:
+64 -112
webapp/src/App.tsx
···
-
import { createSignal, onCleanup, For, type Component } from "solid-js";
+
import { createSignal, onCleanup, For, type Component, Signal } from "solid-js";
import type {} from "@atcute/bluesky";
import type {} from "@atcute/atproto";
-
import { isDid, isHandle } from "@atcute/lexicons/syntax";
-
import { XrpcHandleResolver } from "@atcute/identity-resolver";
-
import { Notification } from "./types.js";
+
import { ConnectionStatus, Notification } from "./types.js";
import { ActivityItem } from "./ActivityItem.jsx";
-
-
const handleResolver = new XrpcHandleResolver({
-
serviceUrl: "https://public.api.bsky.app",
-
});
+
import { connect as connectService } from "./ws.ts";
+
import { Accessor } from "solid-js/types/server/reactive.js";
-
const App: Component = () => {
+
const Wrapped: Component = () => {
const [actorId, setActorId] = createSignal<string>("");
const [serviceDomain, setWsUrl] = createSignal<string>("likes.gaze.systems");
-
const [isConnected, setIsConnected] = createSignal<boolean>(false);
const [items, setItems] = createSignal<Notification[]>([]);
-
const [connectionStatus, setConnectionStatus] = createSignal<
-
"disconnected" | "connecting..." | "connected" | "error"
-
>("disconnected");
+
const [connectionStatus, setConnectionStatus] =
+
createSignal<ConnectionStatus>("disconnected");
const [error, setError] = createSignal<string | null>(null);
-
-
let ws: WebSocket | null = null;
-
-
const connectWebSocket = async () => {
-
const didOrHandle = actorId().trim();
-
const host = serviceDomain().trim();
-
-
setError(null);
-
setConnectionStatus("connecting...");
-
-
let did: string;
-
if (!didOrHandle) {
-
setConnectionStatus("error");
-
setError("please enter a DID or a handle");
-
return;
-
} else if (isHandle(didOrHandle)) {
-
try {
-
did = await handleResolver.resolve(didOrHandle);
-
} catch (error) {
-
setConnectionStatus("error");
-
setError(`can't resolve handle: ${error}`);
-
return;
-
}
-
} else if (isDid(didOrHandle)) {
-
did = didOrHandle;
-
} else {
-
setConnectionStatus("error");
-
setError("inputted DID / handle is not valid");
-
return;
-
}
-
-
if (!host) {
-
setError("please enter service host");
-
setConnectionStatus("error");
-
return;
-
}
-
-
// Close existing connection if any
-
if (ws) {
-
ws.close();
-
}
-
-
let proto = "wss";
-
const domain = host.split(":").at(0) ?? "";
-
if (["localhost", "0.0.0.0", "127.0.0.1"].some((v) => v === domain)) {
-
proto = "ws";
-
}
+
const [ws, setWs] = createSignal<WebSocket | null>(null);
-
const url = `${proto}://${host}/subscribe/${did}`;
+
const connect = async () => {
+
// close existing connection if any
+
ws()?.close();
+
setWs(
+
(await connectService({
+
actorId,
+
pushNotification: (item) => setItems((prev) => [item, ...prev]),
+
serviceDomain,
+
setConnectionStatus,
+
setError,
+
})) ?? null,
+
);
+
};
-
try {
-
ws = new WebSocket(url);
+
const disconnect = (): void => {
+
setConnectionStatus("disconnecting...");
+
ws()?.close();
+
setWs(null);
+
};
-
ws.onopen = () => {
-
setIsConnected(true);
-
setConnectionStatus("connected");
-
setError(null);
-
console.log("WebSocket connected to:", url);
-
};
+
onCleanup(disconnect);
-
ws.onmessage = (event: MessageEvent) => {
-
try {
-
const data: Notification = JSON.parse(event.data);
-
setItems((prev) => [data, ...prev]); // add new items to the top
-
} catch (error) {
-
console.error("Error parsing JSON:", error);
-
}
-
};
+
const props: AppProps = {
+
actorIdSignal: [actorId, setActorId],
+
serviceDomainSignal: [serviceDomain, setWsUrl],
+
itemsSignal: [items, setItems],
+
connectionStatus,
+
error,
+
connect,
+
disconnect,
+
};
-
ws.onclose = () => {
-
setIsConnected(false);
-
setConnectionStatus("disconnected");
-
console.log("WebSocket disconnected");
-
};
+
return <App {...props} />;
+
};
+
export default Wrapped;
-
ws.onerror = (error: Event) => {
-
setConnectionStatus("error");
-
setError(`connection failed: ${error}`);
-
console.error("WebSocket error:", error);
-
};
-
} catch (error) {
-
setConnectionStatus("error");
-
setError(`failed to create connection: ${error}`);
-
console.error("Failed to create WebSocket:", error);
-
}
-
};
+
export interface AppProps {
+
actorIdSignal: Signal<string>;
+
serviceDomainSignal: Signal<string>;
+
itemsSignal: Signal<Notification[]>;
+
connectionStatus: Accessor<ConnectionStatus>;
+
error: Accessor<string | null>;
+
connect: () => void;
+
disconnect: () => void;
+
}
-
const disconnect = (): void => {
-
if (ws) {
-
ws.close();
-
ws = null;
-
}
-
};
+
export const App: Component<AppProps> = (props) => {
+
const [actorId, setActorId] = props.actorIdSignal;
+
const [serviceDomain, setWsUrl] = props.serviceDomainSignal;
+
const [items, setItems] = props.itemsSignal;
+
const { disconnect, connect, error, connectionStatus } = props;
const clearItems = (): void => {
setItems([]);
};
-
onCleanup(() => {
-
if (ws) {
-
ws.close();
-
}
-
});
+
const isConnected = () => {
+
return (
+
connectionStatus() == "connecting..." || connectionStatus() == "connected"
+
);
+
};
return (
-
<div max-w-4xl mx-auto p-6 bg-gray-50 min-h-screen>
+
<div max-w-4xl mx-auto p-4 bg-gray-50 min-h-screen>
<h1 border="l-16 blue" font-bold text="3xl gray-800" pl-2 mb-6>
monitor bluesky repost likes
</h1>
···
onInput={(e) => setWsUrl((e.target as HTMLInputElement).value)}
placeholder="enter service host (e.g., likes.gaze.systems)"
class="flex-1 px-4 py-2 border border-gray-300 rounded-none focus:(outline-none ring-2 ring-purple-500) bg-white"
-
disabled={isConnected() || connectionStatus() == "connecting..."}
+
disabled={isConnected()}
/>
</div>
<div flex gap-2 mb-2>
···
onInput={(e) => setActorId((e.target as HTMLInputElement).value)}
onKeyPress={(e) => {
if (!isConnected() && e.key == "Enter") {
-
connectWebSocket();
+
connect();
e.preventDefault();
}
}}
placeholder="enter handle or DID"
class="flex-1 px-4 py-2 border border-gray-300 rounded-none focus:(outline-none ring-2 ring-blue-500) bg-white"
-
disabled={isConnected() || connectionStatus() == "connecting..."}
+
disabled={isConnected()}
/>
<button
-
onClick={() => (isConnected() ? disconnect() : connectWebSocket())}
+
onClick={() => (isConnected() ? disconnect() : connect())}
class={`px-6 py-2 rounded-none font-medium transition-colors ${
isConnected()
? "bg-red-500 hover:bg-red-600 text-white"
: "bg-blue-500 hover:bg-blue-600 text-white"
}`}
>
-
{isConnected() ? "Disconnect" : "Connect"}
+
{isConnected() ? "disconnect" : "connect"}
</button>
</div>
···
</div>
);
};
-
-
export default App;
+3 -2
webapp/src/index.tsx
···
/* @refresh reload */
import { render } from "solid-js/web";
+
import "solid-devtools";
+
+
import App from "./App.tsx";
import "./index.css";
-
import App from "./App";
-
import "virtual:uno.css";
const root = document.getElementById("root");
+11
webapp/src/lib.d.ts
···
+
import { Component } from "solid-js";
+
import { ConnectionStatus, Notification, NotificationActor } from "./types.ts";
+
import { Callbacks as WsCallbacks, connect } from "./ws.ts";
+
import { AppProps } from "./App.tsx";
+
+
export const App: Component<AppProps>;
+
export const ConnectionStatus: ConnectionStatus;
+
export const Notification: Notification;
+
export const NotificationActor: NotificationActor;
+
export const WebsocketCallbacks: WsCallbacks;
+
export const connectService: typeof connect;
+13
webapp/src/lib.ts
···
+
import "./index.css";
+
import "virtual:uno.css";
+
+
export { App, type AppProps } from "./App.tsx";
+
export type {
+
Notification,
+
NotificationActor,
+
ConnectionStatus,
+
} from "./types.ts";
+
export {
+
type Callbacks as WebsocketCallbacks,
+
connect as connectService,
+
} from "./ws.ts";
+7
webapp/src/types.ts
···
did: Did;
profile?: ProfileViewDetailed;
}
+
+
export type ConnectionStatus =
+
| "disconnected"
+
| "disconnecting..."
+
| "connecting..."
+
| "connected"
+
| "error";
+97
webapp/src/ws.ts
···
+
import { XrpcHandleResolver } from "@atcute/identity-resolver";
+
import { isDid, isHandle } from "@atcute/lexicons/syntax";
+
import { ConnectionStatus, Notification } from "./types.ts";
+
+
export interface Callbacks {
+
setError: (error: string | null) => void;
+
setConnectionStatus: (status: ConnectionStatus) => void;
+
pushNotification: (item: Notification) => void;
+
actorId: () => string;
+
serviceDomain: () => string;
+
}
+
+
const handleResolver = new XrpcHandleResolver({
+
serviceUrl: "https://public.api.bsky.app",
+
});
+
+
export const connect = async (cb: Callbacks) => {
+
const didOrHandle = cb.actorId().trim();
+
const host = cb.serviceDomain().trim();
+
+
cb.setError(null);
+
cb.setConnectionStatus("connecting...");
+
+
let did: string;
+
if (!didOrHandle) {
+
cb.setConnectionStatus("error");
+
cb.setError("please enter a DID or a handle");
+
return;
+
} else if (isHandle(didOrHandle)) {
+
try {
+
did = await handleResolver.resolve(didOrHandle);
+
} catch (error) {
+
cb.setConnectionStatus("error");
+
cb.setError(`can't resolve handle: ${error}`);
+
return;
+
}
+
} else if (isDid(didOrHandle)) {
+
did = didOrHandle;
+
} else {
+
cb.setConnectionStatus("error");
+
cb.setError("inputted DID / handle is not valid");
+
return;
+
}
+
+
if (!host) {
+
cb.setError("please enter service host");
+
cb.setConnectionStatus("error");
+
return;
+
}
+
+
let proto = "wss";
+
const domain = host.split(":").at(0) ?? "";
+
if (["localhost", "0.0.0.0", "127.0.0.1"].some((v) => v === domain)) {
+
proto = "ws";
+
}
+
+
const url = `${proto}://${host}/subscribe/${did}`;
+
+
try {
+
let ws = new WebSocket(url);
+
+
ws.onopen = () => {
+
cb.setConnectionStatus("connected");
+
cb.setError(null);
+
console.log("WebSocket connected to:", url);
+
};
+
+
ws.onmessage = (event: MessageEvent) => {
+
try {
+
cb.pushNotification(JSON.parse(event.data));
+
} catch (error) {
+
console.error("Error parsing JSON:", error);
+
}
+
};
+
+
ws.onclose = () => {
+
cb.setConnectionStatus("disconnected");
+
console.log("WebSocket disconnected");
+
};
+
+
ws.onerror = (error: Event) => {
+
cb.setConnectionStatus("error");
+
cb.setError(`connection failed: ${error}`);
+
console.error("WebSocket error:", error);
+
};
+
+
return ws;
+
} catch (error) {
+
cb.setConnectionStatus("error");
+
cb.setError(`failed to create connection: ${error}`);
+
console.error("Failed to create WebSocket:", error);
+
}
+
+
return;
+
};
+
+
export default connect;
+1
webapp/tsconfig.json
···
"module": "NodeNext",
"moduleResolution": "nodenext",
"allowSyntheticDefaultImports": true,
+
"allowImportingTsExtensions": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
+42
webapp/vite.config.lib.ts
···
+
import { defineConfig } from "vite";
+
import solidPlugin from "vite-plugin-solid";
+
+
import UnoCSS from "unocss/vite";
+
import {
+
presetAttributify,
+
presetWind4,
+
transformerAttributifyJsx,
+
transformerVariantGroup,
+
transformerDirectives,
+
} from "unocss";
+
+
export default defineConfig({
+
plugins: [
+
UnoCSS({
+
presets: [presetWind4(), presetAttributify()],
+
transformers: [
+
transformerVariantGroup(),
+
transformerDirectives(),
+
transformerAttributifyJsx(),
+
],
+
}),
+
solidPlugin(),
+
],
+
build: {
+
target: "esnext",
+
lib: {
+
entry: "./src/lib.ts",
+
name: "bsky-repost-likes-monitor",
+
formats: ["es"],
+
fileName: "index",
+
},
+
rollupOptions: {
+
external: ["solid-js", "solid-js/web"],
+
output: {
+
globals: {
+
"solid-js": "SolidJS",
+
},
+
},
+
},
+
},
+
});
+2
webapp/vite.config.ts
···
import { defineConfig } from "vite";
import solidPlugin from "vite-plugin-solid";
+
import devtools from "solid-devtools/vite";
import UnoCSS from "unocss/vite";
import {
···
transformerAttributifyJsx(),
],
}),
+
devtools({ autoname: true }),
solidPlugin(),
],
server: {