this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

uhhh stuff.

alice c6a63687 9a2cf990

+928 -647
+34
.gitignore
··· 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
+8
.prettierrc
··· 1 + { 2 + "trailingComma": "all", 3 + "tabWidth": 2, 4 + "semi": true, 5 + "singleQuote": true, 6 + "printWidth": 120, 7 + "experimentalTernaries": true 8 + }
+15
README.md
··· 1 + # at-wormhole-chrome 2 + 3 + To install dependencies: 4 + 5 + ```bash 6 + bun install 7 + ``` 8 + 9 + To run: 10 + 11 + ```bash 12 + bun run 13 + ``` 14 + 15 + This project was created using `bun init` in bun v1.2.13. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
+8 -8
_locales/en/messages.json
··· 1 1 { 2 - "extension_name": { 3 - "message": "Wormhole", 4 - "description": "The display name for the extension." 5 - }, 6 - "extension_description": { 7 - "message": "Open at:// links or active tab URL in various BlueSky tools.", 8 - "description": "Description of what the extension does." 9 - } 2 + "extension_name": { 3 + "message": "Wormhole", 4 + "description": "The display name for the extension." 5 + }, 6 + "extension_description": { 7 + "message": "Open at:// links or active tab URL in various BlueSky tools.", 8 + "description": "Description of what the extension does." 9 + } 10 10 }
+210
bun.lock
··· 1 + { 2 + "lockfileVersion": 1, 3 + "workspaces": { 4 + "": { 5 + "name": "at-wormhole-chrome", 6 + "devDependencies": { 7 + "@eslint/js": "^9.27.0", 8 + "@types/bun": "latest", 9 + "@types/chrome": "latest", 10 + "@types/node": "latest", 11 + "eslint": "^9.27.0", 12 + "globals": "^16.1.0", 13 + "prettier": "latest", 14 + }, 15 + }, 16 + }, 17 + "packages": { 18 + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="], 19 + 20 + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], 21 + 22 + "@eslint/config-array": ["@eslint/config-array@0.20.0", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ=="], 23 + 24 + "@eslint/config-helpers": ["@eslint/config-helpers@0.2.2", "", {}, "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg=="], 25 + 26 + "@eslint/core": ["@eslint/core@0.14.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg=="], 27 + 28 + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], 29 + 30 + "@eslint/js": ["@eslint/js@9.27.0", "", {}, "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA=="], 31 + 32 + "@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="], 33 + 34 + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.1", "", { "dependencies": { "@eslint/core": "^0.14.0", "levn": "^0.4.1" } }, "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w=="], 35 + 36 + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], 37 + 38 + "@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="], 39 + 40 + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], 41 + 42 + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], 43 + 44 + "@types/bun": ["@types/bun@1.2.13", "", { "dependencies": { "bun-types": "1.2.13" } }, "sha512-u6vXep/i9VBxoJl3GjZsl/BFIsvML8DfVDO0RYLEwtSZSp981kEO1V5NwRcO1CPJ7AmvpbnDCiMKo3JvbDEjAg=="], 45 + 46 + "@types/chrome": ["@types/chrome@0.0.323", "", { "dependencies": { "@types/filesystem": "*", "@types/har-format": "*" } }, "sha512-ipiDwx41lmGeLnbiT6ENOayvWXdkqKqNwqDQWEuz6dujaX7slSkk1nbSt5Q5c6xnQ708+kuCFrC00VLltSbWVA=="], 47 + 48 + "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], 49 + 50 + "@types/filesystem": ["@types/filesystem@0.0.36", "", { "dependencies": { "@types/filewriter": "*" } }, "sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA=="], 51 + 52 + "@types/filewriter": ["@types/filewriter@0.0.33", "", {}, "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g=="], 53 + 54 + "@types/har-format": ["@types/har-format@1.2.16", "", {}, "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A=="], 55 + 56 + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], 57 + 58 + "@types/node": ["@types/node@22.15.18", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg=="], 59 + 60 + "acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], 61 + 62 + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], 63 + 64 + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], 65 + 66 + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], 67 + 68 + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], 69 + 70 + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], 71 + 72 + "brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], 73 + 74 + "bun-types": ["bun-types@1.2.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-rRjA1T6n7wto4gxhAO/ErZEtOXyEZEmnIHQfl0Dt1QQSB4QV0iP6BZ9/YB5fZaHFQ2dwHFrmPaRQ9GGMX01k9Q=="], 75 + 76 + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], 77 + 78 + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], 79 + 80 + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], 81 + 82 + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 83 + 84 + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], 85 + 86 + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], 87 + 88 + "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], 89 + 90 + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], 91 + 92 + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], 93 + 94 + "eslint": ["eslint@9.27.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.20.0", "@eslint/config-helpers": "^0.2.1", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.27.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q=="], 95 + 96 + "eslint-scope": ["eslint-scope@8.3.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ=="], 97 + 98 + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="], 99 + 100 + "espree": ["espree@10.3.0", "", { "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.0" } }, "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg=="], 101 + 102 + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], 103 + 104 + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], 105 + 106 + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], 107 + 108 + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], 109 + 110 + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], 111 + 112 + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], 113 + 114 + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], 115 + 116 + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], 117 + 118 + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], 119 + 120 + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], 121 + 122 + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], 123 + 124 + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], 125 + 126 + "globals": ["globals@16.1.0", "", {}, "sha512-aibexHNbb/jiUSObBgpHLj+sIuUmJnYcgXBlrfsiDZ9rt4aF2TFRbyLgZ2iFQuVZ1K5Mx3FVkbKRSgKrbK3K2g=="], 127 + 128 + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], 129 + 130 + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], 131 + 132 + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], 133 + 134 + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], 135 + 136 + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], 137 + 138 + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], 139 + 140 + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], 141 + 142 + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], 143 + 144 + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], 145 + 146 + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], 147 + 148 + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], 149 + 150 + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], 151 + 152 + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], 153 + 154 + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], 155 + 156 + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], 157 + 158 + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], 159 + 160 + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 161 + 162 + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], 163 + 164 + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], 165 + 166 + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], 167 + 168 + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], 169 + 170 + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], 171 + 172 + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], 173 + 174 + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], 175 + 176 + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], 177 + 178 + "prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="], 179 + 180 + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], 181 + 182 + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], 183 + 184 + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], 185 + 186 + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], 187 + 188 + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], 189 + 190 + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], 191 + 192 + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], 193 + 194 + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], 195 + 196 + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], 197 + 198 + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], 199 + 200 + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], 201 + 202 + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], 203 + 204 + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], 205 + 206 + "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], 207 + 208 + "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], 209 + } 210 + }
+9
eslint.config.mjs
··· 1 + import js from '@eslint/js'; 2 + import globals from 'globals'; 3 + import { defineConfig } from 'eslint/config'; 4 + 5 + export default defineConfig([ 6 + { files: ['**/*.{js,mjs,cjs}'], plugins: { js }, extends: ['js/recommended'] }, 7 + { files: ['**/*.js'], languageOptions: { sourceType: 'commonjs' } }, 8 + { files: ['**/*.{js,mjs,cjs}'], languageOptions: { globals: { ...globals.browser, ...globals.node } } }, 9 + ]);
images/icon-128.png

This is a binary file and will not be displayed.

images/icon-256.png

This is a binary file and will not be displayed.

images/icon-48.png

This is a binary file and will not be displayed.

images/icon-512.png

This is a binary file and will not be displayed.

images/icon-64.png

This is a binary file and will not be displayed.

images/icon-96.png

This is a binary file and will not be displayed.

images/icon_120.png

This is a binary file and will not be displayed.

images/icon_120_opaque.png

This is a binary file and will not be displayed.

images/icon_128.png

This is a binary file and will not be displayed.

images/icon_128_opaque.png

This is a binary file and will not be displayed.

images/icon_152.png

This is a binary file and will not be displayed.

images/icon_152_opaque.png

This is a binary file and will not be displayed.

images/icon_16.png

This is a binary file and will not be displayed.

images/icon_167.png

This is a binary file and will not be displayed.

images/icon_167_opaque.png

This is a binary file and will not be displayed.

images/icon_16_opaque.png

This is a binary file and will not be displayed.

images/icon_180.png

This is a binary file and will not be displayed.

images/icon_180_opaque.png

This is a binary file and will not be displayed.

images/icon_19.png

This is a binary file and will not be displayed.

images/icon_19_opaque.png

This is a binary file and will not be displayed.

images/icon_20.png

This is a binary file and will not be displayed.

images/icon_20_opaque.png

This is a binary file and will not be displayed.

images/icon_256.png

This is a binary file and will not be displayed.

images/icon_256_opaque.png

This is a binary file and will not be displayed.

images/icon_29.png

This is a binary file and will not be displayed.

images/icon_29_opaque.png

This is a binary file and will not be displayed.

images/icon_32.png

This is a binary file and will not be displayed.

images/icon_32_opaque.png

This is a binary file and will not be displayed.

images/icon_38.png

This is a binary file and will not be displayed.

images/icon_38_opaque.png

This is a binary file and will not be displayed.

images/icon_40.png

This is a binary file and will not be displayed.

images/icon_40_opaque.png

This is a binary file and will not be displayed.

images/icon_48.png

This is a binary file and will not be displayed.

images/icon_48_opaque.png

This is a binary file and will not be displayed.

images/icon_512.png

This is a binary file and will not be displayed.

images/icon_512_opaque.png

This is a binary file and will not be displayed.

images/icon_58.png

This is a binary file and will not be displayed.

images/icon_58_opaque.png

This is a binary file and will not be displayed.

images/icon_60.png

This is a binary file and will not be displayed.

images/icon_60_opaque.png

This is a binary file and will not be displayed.

images/icon_76.png

This is a binary file and will not be displayed.

images/icon_76_opaque.png

This is a binary file and will not be displayed.

images/icon_80.png

This is a binary file and will not be displayed.

images/icon_80_opaque.png

This is a binary file and will not be displayed.

images/icon_87.png

This is a binary file and will not be displayed.

images/icon_87_opaque.png

This is a binary file and will not be displayed.

images/icon_96.png

This is a binary file and will not be displayed.

images/icon_96_opaque.png

This is a binary file and will not be displayed.

-5
images/toolbar-icon.svg
··· 1 - <?xml version="1.0" encoding="UTF-8"?> 2 - <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.9375 15.9453"> 3 - <path d="M7.96875 15.9375C12.3281 15.9375 15.9375 12.3203 15.9375 7.96875C15.9375 3.60938 12.3203 0 7.96094 0C3.60938 0 0 3.60938 0 7.96875C0 12.3203 3.61719 15.9375 7.96875 15.9375ZM7.96875 14.6094C4.28125 14.6094 1.33594 11.6562 1.33594 7.96875C1.33594 4.28125 4.27344 1.32812 7.96094 1.32812C11.6484 1.32812 14.6094 4.28125 14.6094 7.96875C14.6094 11.6562 11.6562 14.6094 7.96875 14.6094Z" /> 4 - <path d="M4.53906 8.50781C4.53906 8.70312 4.69531 8.84375 4.89844 8.84375L7.54688 8.84375L6.13281 12.6406C5.94531 13.1406 6.47656 13.4141 6.80469 13.0078L11.0859 7.63281C11.1719 7.53906 11.2188 7.42969 11.2188 7.32812C11.2188 7.13281 11.0625 6.99219 10.8594 6.99219L8.21094 6.99219L9.625 3.19531C9.8125 2.69531 9.28125 2.42188 8.95312 2.82031L4.67188 8.19531C4.58594 8.29688 4.53906 8.40625 4.53906 8.50781Z" /> 5 - </svg>
+39 -31
manifest.json
··· 1 1 { 2 - "manifest_version": 3, 3 - "default_locale": "en", 2 + "manifest_version": 3, 3 + "default_locale": "en", 4 4 5 - "name": "__MSG_extension_name__", 6 - "description": "__MSG_extension_description__", 7 - "version": "1.0", 5 + "name": "__MSG_extension_name__", 6 + "description": "__MSG_extension_description__", 7 + "version": "1.0", 8 8 9 - "icons": { 10 - "48": "images/icon-48.png", 11 - "96": "images/icon-96.png", 12 - "128": "images/icon-128.png", 13 - "256": "images/icon-256.png", 14 - "512": "images/icon-512.png" 15 - }, 16 - 17 - "permissions": [ 18 - "activeTab", 19 - "contextMenus", 20 - "tabs", 21 - "storage" 22 - ], 23 - "host_permissions": [ 24 - "<all_urls>" 25 - ], 9 + "icons": { 10 + "48": "images/icon_48.png", 11 + "96": "images/icon_96.png", 12 + "128": "images/icon_128.png", 13 + "256": "images/icon_256.png", 14 + "512": "images/icon_512.png" 15 + }, 26 16 27 - "background": { 28 - "service_worker": "service-worker.js" 29 - }, 17 + "permissions": ["activeTab", "contextMenus", "tabs", "storage"], 18 + "host_permissions": [ 19 + "https://public.api.bsky.app/*", 20 + "https://plc.directory/*", 21 + "https://bsky.app/*", 22 + "https://deer.social/*", 23 + "https://pdsls.dev/*", 24 + "https://atp.tools/*", 25 + "https://clearsky.app/*", 26 + "https://blue.mackuba.eu/*", 27 + "https://cred.blue/*", 28 + "https://tangled.sh/*", 29 + "https://frontpage.fyi/*", 30 + "https://boat.kelinci.net/*" 31 + ], 32 + 33 + "background": { 34 + "service_worker": "service-worker.js" 35 + }, 30 36 31 - "action": { 32 - "default_popup": "popup.html", 33 - "default_icon": { 34 - "48": "images/icon-48.png", 35 - "96": "images/icon-96.png" 36 - } 37 + "action": { 38 + "default_popup": "popup.html", 39 + "default_icon": { 40 + "16": "images/icon_16.png", 41 + "19": "images/icon_19.png", 42 + "48": "images/icon_48.png", 43 + "96": "images/icon_96.png" 37 44 } 45 + } 38 46 }
+19
package.json
··· 1 + { 2 + "name": "at-wormhole-chrome", 3 + "private": true, 4 + "scripts": { 5 + "lint": "bunx eslint .", 6 + "lint:fix": "bunx eslint . --fix", 7 + "format": "bunx prettier --ignore-unknown --write .", 8 + "test": "bun run test.js" 9 + }, 10 + "devDependencies": { 11 + "@eslint/js": "^9.27.0", 12 + "@types/bun": "latest", 13 + "@types/chrome": "latest", 14 + "@types/node": "latest", 15 + "eslint": "^9.27.0", 16 + "globals": "^16.1.0", 17 + "prettier": "latest" 18 + } 19 + }
+41 -21
popup.html
··· 1 - <!DOCTYPE html> 1 + <!doctype html> 2 2 <html> 3 - <head> 4 - <meta charset="utf-8"> 5 - <title>Wormhole</title> 6 - <style> 7 - body { font-family: -apple-system, sans-serif; margin: 0; padding: 10px; width: 280px; } 8 - ul { list-style: none; padding: 0; } 9 - li { margin: 4px 0; } 10 - button { width: 100%; text-align: left; padding: 6px 8px; border: 1px solid #ccc; border-radius: 6px; background: #fafafa; font-size: 14px; } 11 - button:hover { background: #eee; } 12 - </style> 13 - </head> 14 - <body> 15 - <p>Wormhole!</p> 16 - <ul id="dest"></ul> 17 - <hr style="margin-top: 10px; margin-bottom: 10px;"/> 18 - <div style="text-align: center;"> 19 - <button id="emptyCacheBtn" style="padding: 4px 8px; font-size: 12px;">Empty Cache</button> 20 - </div> 21 - <script src="transform.js"></script> 3 + <head> 4 + <meta charset="utf-8" /> 5 + <title>Wormhole</title> 6 + <style> 7 + body { 8 + font-family: -apple-system, sans-serif; 9 + margin: 0; 10 + padding: 10px; 11 + width: 280px; 12 + } 13 + ul { 14 + list-style: none; 15 + padding: 0; 16 + } 17 + li { 18 + margin: 4px 0; 19 + } 20 + button { 21 + width: 100%; 22 + text-align: left; 23 + padding: 6px 8px; 24 + border: 1px solid #ccc; 25 + border-radius: 6px; 26 + background: #fafafa; 27 + font-size: 14px; 28 + } 29 + button:hover { 30 + background: #eee; 31 + } 32 + </style> 33 + </head> 34 + <body> 35 + <p>Wormhole!</p> 36 + <ul id="dest"></ul> 37 + <hr style="margin-top: 10px; margin-bottom: 10px" /> 38 + <div style="text-align: center"> 39 + <button id="emptyCacheBtn" style="padding: 4px 8px; font-size: 12px">Empty Cache</button> 40 + </div> 41 + <script src="transform.js"></script> 42 + </body> 22 43 <script src="popup.js"></script> 23 - </body> 24 44 </html>
+94 -148
popup.js
··· 1 - (async function () { 2 - console.log("Popup script started"); // Log: Script start 3 - const params = new URLSearchParams(window.location.search); 4 - let raw = params.get("payload"); 5 - console.log("Initial raw payload:", raw); // Log: Raw payload 6 - if (!raw) { 7 - const [tab] = await chrome.tabs.query({ 8 - active: true, 9 - currentWindow: true, 10 - }); 11 - raw = tab.url; 12 - console.log("Raw payload from active tab:", raw); // Log: Tab URL payload 13 - } 1 + /* global chrome */ 2 + 3 + document.addEventListener('DOMContentLoaded', async () => { 4 + const list = document.getElementById('dest'); 5 + const emptyBtn = document.getElementById('emptyCacheBtn'); 6 + 7 + const showStatus = (msg) => (list.innerHTML = `<li>${msg}</li>`); 8 + const createItem = ({ url, label }) => ` 9 + <li> 10 + <a href="${url}" target="_blank" rel="noopener noreferrer" 11 + style="display:block;padding:6px 8px;border:1px solid #ccc;border-radius:6px;background:#fafafa;text-decoration:none;color:inherit;font-size:14px;"> 12 + ${label} 13 + </a> 14 + </li>`; 15 + const render = (ds) => 16 + ds && ds.length ? (list.innerHTML = ds.map(createItem).join('')) : showStatus('No actions available'); 14 17 15 - const list = document.getElementById("dest"); 16 - list.innerHTML = "<li>Processing...</li>"; 18 + const rawParam = new URLSearchParams(location.search).get('payload'); 19 + let raw = rawParam || (await chrome.tabs.query({ active: true, currentWindow: true }))[0].url; 17 20 18 - // Ensure WormholeTransform and its methods are available 19 - if ( 20 - !window.WormholeTransform || 21 - typeof window.WormholeTransform.parseInput !== "function" 22 - ) { 23 - list.innerHTML = "<li>Error: Transform script not loaded correctly.</li>"; 24 - console.error("Popup: WormholeTransform.parseInput is not available."); 25 - return; 21 + if (!window.WormholeTransform?.parseInput) { 22 + showStatus('Error: transform not loaded'); 23 + return; // Exit if critical component is missing 26 24 } 27 25 28 26 const info = await window.WormholeTransform.parseInput(raw); 29 - console.log("Parsed info:", JSON.stringify(info, null, 2)); // Log: Parsed info object 30 - 31 - if (!info || (!info.did && !info.atUri)) { 32 - list.innerHTML = "<li>No DID or at:// reference found in the input.</li>"; 33 - console.log("No DID or atUri found in info, exiting."); // Log: Exit condition 34 - return; 27 + if (!info?.did && !info?.atUri) { 28 + showStatus('No DID or at:// found'); 29 + return; // Exit if no relevant data 35 30 } 36 31 37 - const DID_HANDLE_CACHE_KEY = "didHandleCache"; // Caching re-enabled 32 + let ds = window.WormholeTransform.buildDestinations(info); 33 + render(ds); 38 34 39 - const renderDestinations = (destinations) => { 40 - console.log( 41 - "Rendering destinations:", 42 - JSON.stringify(destinations, null, 2) 43 - ); // Log: Destinations to render 44 - if (destinations && destinations.length > 0) { 45 - list.innerHTML = destinations 46 - .map( 47 - (d) => 48 - `<li> 49 - <a href="${d.url}" 50 - target="_blank" 51 - rel="noopener noreferrer" 52 - style="display:block; padding:6px 8px; border:1px solid #ccc; border-radius:6px; background:#fafafa; text-decoration:none; color:inherit; font-size:14px;"> 53 - ${d.label} 54 - </a> 55 - </li>` 56 - ) 57 - .join(""); 58 - } else { 59 - list.innerHTML = "<li>No actions available at this time.</li>"; 60 - console.log("No destinations to render."); // Log: No destinations 61 - } 62 - }; 63 - 64 - // Initial render based on whatever info.handle might exist (e.g. from direct handle input) 65 - let currentDests = window.WormholeTransform.buildDestinations(info); 66 - renderDestinations(currentDests); 67 - 68 - // If a DID is present but the handle is not, try to resolve or get from cache. 69 35 if (info.did && !info.handle) { 70 - console.log( 71 - `Popup: DID ${info.did} present, handle missing. Checking cache...` 72 - ); 73 - 74 - const cacheData = await chrome.storage.local.get(DID_HANDLE_CACHE_KEY); 75 - const cachedHandles = cacheData[DID_HANDLE_CACHE_KEY] || {}; 36 + const { didHandleCache = {} } = await chrome.storage.local.get('didHandleCache'); 37 + const cachedHandleValue = didHandleCache[info.did]; 76 38 77 - if (cachedHandles[info.did]) { 78 - console.log( 79 - `Popup: Found handle '${cachedHandles[info.did]}' for DID ${ 80 - info.did 81 - } in cache.` 82 - ); 83 - info.handle = cachedHandles[info.did]; 84 - currentDests = window.WormholeTransform.buildDestinations(info); 85 - renderDestinations(currentDests); 39 + if (typeof cachedHandleValue === 'string') { 40 + // Cached handle is a string, use it 41 + info.handle = cachedHandleValue; 42 + ds = window.WormholeTransform.buildDestinations(info); // Re-build ds with the handle 43 + render(ds); // Re-render 86 44 } else { 87 - console.log( 88 - `Popup: Handle for DID ${info.did} not in cache. Attempting to resolve...` 89 - ); 90 - if (!currentDests || currentDests.length === 0) { 91 - list.innerHTML = 92 - "<li>Resolving identifier to check for more actions...</li>"; 45 + // Handle is not in cache as a string (either undefined or a non-string type) 46 + if (cachedHandleValue !== undefined) { 47 + // It existed but was not a string, log it. 48 + console.warn('Cached handle for DID', info.did, 'was not a string, re-fetching. Value:', cachedHandleValue); 93 49 } 94 - try { 95 - if (typeof window.WormholeTransform.resolveDidToHandle !== "function") { 96 - list.innerHTML = "<li>Error: Resolve function not loaded.</li>"; 97 - console.error( 98 - "Popup: WormholeTransform.resolveDidToHandle is not available." 99 - ); 100 - return; 101 - } 102 - const resolvedHandle = 103 - await window.WormholeTransform.resolveDidToHandle(info.did); 104 - console.log( 105 - `Popup: Resolved handle: ${resolvedHandle} for DID: ${info.did}` 106 - ); 107 - if (resolvedHandle) { 108 - info.handle = resolvedHandle; 109 - currentDests = window.WormholeTransform.buildDestinations(info); 110 - console.log("Popup: Re-rendering destinations with resolved handle."); 111 - renderDestinations(currentDests); 50 + 51 + // Show "Resolving..." if list is empty OR if we are about to overwrite a bad/missing cache entry 52 + // The condition `!ds.length` comes from the original code structure. 53 + // The condition `cachedHandleValue !== undefined` ensures "Resolving..." is shown if we just invalidated a non-string cache entry. 54 + if (!ds.length || cachedHandleValue !== undefined) { 55 + showStatus('Resolving...'); 56 + } 112 57 113 - // Update cache with the newly resolved handle 114 - const updatedCachedHandles = { 115 - ...cachedHandles, 116 - [info.did]: resolvedHandle, 117 - }; 118 - await chrome.storage.local.set({ 119 - [DID_HANDLE_CACHE_KEY]: updatedCachedHandles, 120 - }); 121 - console.log( 122 - `Popup: Saved resolved handle ${resolvedHandle} for DID ${info.did} to cache.` 123 - ); 58 + try { 59 + if (typeof window.WormholeTransform.resolveDidToHandle !== 'function') { 60 + showStatus('Error: resolve fn missing'); 124 61 } else { 125 - if (!currentDests || currentDests.length === 0) { 126 - list.innerHTML = 127 - "<li>Could not resolve identifier. No actions available.</li>"; 62 + const freshHandle = await window.WormholeTransform.resolveDidToHandle(info.did); // freshHandle is a string 63 + if (freshHandle) { 64 + info.handle = freshHandle; 65 + ds = window.WormholeTransform.buildDestinations(info); // Re-build ds 66 + render(ds); // Re-render 67 + // Store the correct string format in cache, potentially overwriting a bad entry 68 + await chrome.storage.local.set({ didHandleCache: { ...didHandleCache, [info.did]: freshHandle } }); 69 + } else if (!ds.length) { 70 + // Only show "No actions" if list is still empty after failed fetch 71 + showStatus('No actions available'); 128 72 } 129 - console.log( 130 - `Popup: Handle resolution returned null/false for DID: ${info.did}` 131 - ); 132 73 } 133 - } catch (error) { 134 - console.error("Popup: Error resolving handle:", error); 135 - if (!currentDests || currentDests.length === 0) { 136 - list.innerHTML = 137 - "<li>Error resolving identifier. No actions available.</li>"; 138 - } 74 + } catch (err) { 75 + console.error('Error resolving DID to handle:', err); 76 + showStatus('Error resolving'); // Show error status 139 77 } 140 78 } 141 - } else if (info.did && info.handle) { 142 - console.log( 143 - "Popup: DID and handle already present in info, no resolution or cache check needed.", 144 - JSON.stringify(info, null, 2) 145 - ); 146 - } else { 147 - console.log( 148 - "Popup: Condition for handle resolution/cache check not met (no DID or handle already present)." 149 - ); 150 79 } 151 80 152 - // Add event listener for the Empty Cache button 153 - const emptyCacheButton = document.getElementById("emptyCacheBtn"); 154 - if (emptyCacheButton) { 155 - emptyCacheButton.addEventListener("click", async () => { 81 + if (emptyBtn) { 82 + emptyBtn.addEventListener('click', async (e) => { 83 + e.preventDefault(); 84 + e.stopPropagation(); 85 + 86 + const originalText = emptyBtn.textContent; 87 + emptyBtn.textContent = 'Working...'; 88 + emptyBtn.disabled = true; 89 + 156 90 try { 157 - await chrome.storage.local.remove(DID_HANDLE_CACHE_KEY); 158 - console.log("Popup: DID Handle Cache Cleared!"); 159 - // Optionally, provide user feedback in the popup itself, e.g., change button text 160 - emptyCacheButton.textContent = "Cache Cleared!"; 91 + await chrome.storage.local.remove('didHandleCache'); // Old key 92 + 93 + await new Promise((resolve, reject) => { 94 + chrome.runtime.sendMessage({ type: 'CLEAR_CACHE' }, (msgResponse) => { 95 + if (chrome.runtime.lastError) { 96 + console.error('Runtime error sending message:', chrome.runtime.lastError); 97 + return reject(new Error(chrome.runtime.lastError.message)); 98 + } 99 + if (msgResponse && msgResponse.success) { 100 + resolve(msgResponse); 101 + } else { 102 + reject(new Error(msgResponse?.error || 'Unknown error from service worker')); 103 + } 104 + }); 105 + }); 106 + emptyBtn.textContent = 'Cleared'; 107 + } catch (error) { 108 + console.error('Failed to clear cache:', error); 109 + emptyBtn.textContent = 'Error'; 110 + } finally { 161 111 setTimeout(() => { 162 - emptyCacheButton.textContent = "Empty Cache"; 112 + emptyBtn.textContent = originalText; 113 + emptyBtn.disabled = false; 163 114 }, 1500); 164 - // You might want to re-render or force a re-check if the current view depends on cached data 165 - // For now, it just clears it. Next time popup opens with a relevant DID, it will miss cache. 166 - } catch (error) { 167 - console.error("Popup: Error clearing cache:", error); 168 - emptyCacheButton.textContent = "Error Clearing!"; 169 115 } 170 116 }); 171 117 } else { 172 - console.warn("Popup: Empty Cache button not found."); 118 + console.error('emptyCacheBtn NOT FOUND even within DOMContentLoaded.'); 173 119 } 120 + }); 174 121 175 - console.log("Popup script finished (caching re-enabled)."); // Log: Script end 176 - })(); 122 + console.log('popup.js script finished initial global execution.');
+72
refactor-plan.md
··· 1 + # Refactor Plan for at-wormhole-chrome 2 + 3 + ## Goals 4 + 5 + - Remove noise (console.logs, superfluous comments) 6 + - Extract reusable helpers & constants 7 + - Flatten control flow 8 + - Improve maintainability & readability 9 + - Ensure consistent logging (debug/production toggle) 10 + - Add linting & formatting 11 + 12 + ## File-by-file breakdown 13 + 14 + ### 1. popup.js 15 + 16 + - Extract utility functions to `utils.js` 17 + - Centralize constants (`CACHE_KEY`, selectors, templates) 18 + - Replace repeated `list.innerHTML = ...` with `renderStatus(msg)` 19 + - Abstract template for destination items 20 + - Consolidate DID→handle resolution into one function 21 + - Remove verbose console.logs; use `log.debug` wrapper 22 + 23 + ### 2. popup.html 24 + 25 + - Move inline styles to CSS file (`popup.css`) 26 + - Remove unnecessary comments & whitespace 27 + - Add class names in markup to match JS selectors 28 + 29 + ### 3. service-worker.js 30 + 31 + - Extract storage helpers (`getCache`, `setCache`) 32 + - Remove debug logs; add error logging only 33 + - Flatten async flow; use early return 34 + - Add JSDoc only where needed 35 + 36 + ### 4. background.js 37 + 38 + - Extract helpers, remove logs, flatten async 39 + - Consistent error handling 40 + 41 + ### 5. transform.js 42 + 43 + - Remove unused/commented code 44 + - Group exports at bottom 45 + - Simplify parsing logic; drop redundant variables 46 + - Add error wrapping 47 + 48 + ### 6. test.js 49 + 50 + - Convert to Jest/Mocha style tests 51 + - Remove console.logs; use assertions only 52 + - Extract test fixtures 53 + 54 + ### 7. manifest.json 55 + 56 + - Alphabetize keys; remove unused fields 57 + - Minimize permissions 58 + 59 + ### 8. Tooling & CI 60 + 61 + - Add ESLint + Prettier configs 62 + - Npm scripts: `lint`, `test` 63 + - CI workflow (`.github/workflows/ci.yml`) 64 + 65 + ## Stages 66 + 67 + 1. **Init**: Create `utils.js`, add logging wrapper, set up linting. [✔️ Completed: Used existing `eslint.config.mjs`; lint issues fixed; removed custom `.eslintrc.js`] 68 + 2. **Core**: Refactor `popup.js` & `transform.js`. [✔️ `popup.js` refactored and rolled back; styling restored; transform.js pending] 69 + 3. **Workers**: Refactor `service-worker.js` & `background.js` 70 + 4. **UI**: Update `popup.html` & add `popup.css` 71 + 5. **Tests**: Migrate `test.js` to framework 72 + 6. **Final**: QA, manual testing, version bump
+162 -39
service-worker.js
··· 1 - // service-worker.js 1 + /* globals chrome, importScripts, parseInput, resolveDidToHandle */ 2 2 3 - // Try to import transform.js. 4 - // In Manifest V3, for service workers, direct global function exposure from importScripts is typical. 5 - // If transform.js is structured to put its functions on a global (e.g., self.WormholeTransform), 6 - // we'd use that. Assuming it makes functions globally available for now. 7 3 try { 8 4 importScripts('transform.js'); 9 5 } catch (e) { 10 - console.error("Failed to import transform.js in service-worker:", e); 6 + console.error('SW import fail', e); 11 7 } 12 8 13 - const DID_HANDLE_CACHE_KEY = 'didHandleCache'; // Caching re-enabled 9 + const DID_HANDLE_CACHE_KEY = 'didHandleCache'; 10 + const MAX_CACHE_ENTRIES = 1000; // Maximum number of entries to keep in the cache 11 + const CLEANUP_THRESHOLD = 1.2 * MAX_CACHE_ENTRIES; // Start cleanup when we're 20% over limit 14 12 15 - chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => { 16 - // Ensure functions are available (they should be if importScripts worked) 17 - if (typeof parseInput !== 'function' || typeof resolveDidToHandle !== 'function') { 18 - console.error('Service Worker: parseInput or resolveDidToHandle not defined. Check transform.js import.'); 19 - return; 13 + // Structure: { [did]: { handle: string, lastAccessed: number } } 14 + 15 + /** 16 + * Updates the cache with a new entry and ensures it doesn't exceed the maximum size 17 + */ 18 + // Use a simple LRU cache with Map for better performance 19 + let cache = new Map(); 20 + let isCacheDirty = false; 21 + let cleanupScheduled = false; 22 + 23 + // Load cache from storage on startup 24 + async function loadCache() { 25 + try { 26 + const result = await chrome.storage.local.get(DID_HANDLE_CACHE_KEY); 27 + const stored = result[DID_HANDLE_CACHE_KEY] || {}; 28 + cache = new Map(Object.entries(stored)); 29 + } catch (error) { 30 + console.error('Failed to load cache:', error); 20 31 } 32 + } 21 33 22 - if (changeInfo.status === 'complete' && tab.url) { 23 - console.log(`Service Worker: Tab ${tabId} updated to complete, URL: ${tab.url}`); 24 - try { 25 - const info = await parseInput(tab.url); 26 - if (info && info.did && !info.handle) { // We have a DID but no immediate handle 27 - console.log(`Service Worker: Found DID ${info.did} from ${tab.url}, attempting to resolve handle for pre-caching.`); 28 - 29 - // Check if we already have this DID in cache to avoid unnecessary re-fetching by the service worker itself 30 - // The popup will have its own logic to prefer cache then fetch. 31 - const cache = await chrome.storage.local.get(DID_HANDLE_CACHE_KEY); 32 - const currentCache = cache[DID_HANDLE_CACHE_KEY] || {}; 33 - if (currentCache[info.did]) { 34 - console.log(`Service Worker: Handle for DID ${info.did} already in cache, no pre-fetch needed by worker.`); 35 - return; 36 - } 34 + // Save cache to storage, but debounce to prevent frequent writes 35 + let saveTimeout = null; 36 + async function saveCache() { 37 + isCacheDirty = true; 37 38 38 - const resolvedHandle = await resolveDidToHandle(info.did); 39 + if (saveTimeout) clearTimeout(saveTimeout); 39 40 40 - if (resolvedHandle) { 41 - console.log(`Service Worker: Successfully resolved DID ${info.did} to handle ${resolvedHandle}. Caching.`); 42 - const newCacheEntry = { [info.did]: resolvedHandle }; 43 - const updatedCache = { ...currentCache, ...newCacheEntry }; 44 - await chrome.storage.local.set({ [DID_HANDLE_CACHE_KEY]: updatedCache }); 45 - console.log(`Service Worker: DID ${info.did} -> Handle ${resolvedHandle} saved to cache.`); 46 - } else { 47 - console.log(`Service Worker: Could not resolve handle for DID ${info.did} from ${tab.url}. Not caching.`); 41 + // Debounce saves to prevent excessive writes 42 + return new Promise((resolve) => { 43 + saveTimeout = setTimeout(async () => { 44 + if (isCacheDirty) { 45 + try { 46 + await chrome.storage.local.set({ 47 + [DID_HANDLE_CACHE_KEY]: Object.fromEntries(cache), 48 + }); 49 + isCacheDirty = false; 50 + } catch (error) { 51 + console.error('Failed to save cache:', error); 52 + if (error.message.includes('QUOTA_BYTES')) { 53 + await clearOldCacheEntries(0.5); 54 + return saveCache().then(resolve); 55 + } 48 56 } 49 57 } 50 - } catch (error) { 51 - console.error(`Service Worker: Error processing tab update for ${tab.url}:`, error); 58 + resolve(); 59 + }, 1000); // 1s debounce 60 + }); 61 + } 62 + 63 + async function updateCache(did, handle) { 64 + try { 65 + // Update the in-memory cache 66 + cache.set(did, { handle, lastAccessed: Date.now() }); 67 + 68 + // Schedule a cleanup if needed 69 + if (cache.size > CLEANUP_THRESHOLD && !cleanupScheduled) { 70 + cleanupScheduled = true; 71 + // Run cleanup on the next tick to avoid blocking the UI 72 + setTimeout(() => { 73 + cleanupCache().finally(() => { 74 + cleanupScheduled = false; 75 + }); 76 + }, 0); 52 77 } 78 + 79 + // Schedule a save to storage 80 + await saveCache(); 81 + } catch (error) { 82 + console.error('Failed to update cache:', error); 53 83 } 84 + } 85 + 86 + /** 87 + * Clears the oldest entries from the cache 88 + * @param {number} ratio - Fraction of the cache to clear (0-1) 89 + */ 90 + async function cleanupCache() { 91 + if (cache.size <= MAX_CACHE_ENTRIES) return; 92 + 93 + try { 94 + // Convert to array, sort, and keep only the most recent entries 95 + const sorted = Array.from(cache.entries()).sort((a, b) => a[1].lastAccessed - b[1].lastAccessed); 96 + 97 + // Keep only the most recent MAX_CACHE_ENTRIES 98 + const toKeep = sorted.slice(-MAX_CACHE_ENTRIES); 99 + 100 + // Update the cache 101 + cache = new Map(toKeep); 102 + 103 + // Save the cleaned-up cache 104 + await saveCache(); 105 + } catch (error) { 106 + console.error('Failed to clean up cache:', error); 107 + } 108 + } 109 + 110 + // For backward compatibility with the existing code 111 + async function clearOldCacheEntries(ratio = 0.5) { 112 + if (cache.size === 0) return; 113 + 114 + try { 115 + const entries = Array.from(cache.entries()); 116 + const sorted = entries.sort((a, b) => a[1].lastAccessed - b[1].lastAccessed); 117 + const toKeep = sorted.slice(-Math.floor(entries.length * (1 - ratio))); 118 + 119 + cache = new Map(toKeep); 120 + await saveCache(); 121 + } catch (error) { 122 + console.error('Failed to clear old cache entries:', error); 123 + } 124 + } 125 + 126 + // Initialize the cache when the service worker starts 127 + loadCache().catch(console.error); 128 + 129 + // Handle messages from the popup 130 + chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { 131 + if (request.type === 'CLEAR_CACHE') { 132 + // Clear the in-memory cache 133 + cache.clear(); 134 + // Clear the storage 135 + chrome.storage.local 136 + .remove(DID_HANDLE_CACHE_KEY) 137 + .then(() => { 138 + sendResponse({ success: true }); 139 + }) 140 + .catch((error) => { 141 + console.error('Failed to clear storage cache:', error); 142 + sendResponse({ 143 + success: false, 144 + error: error.message, 145 + }); 146 + }); 147 + 148 + return true; // Keep the message channel open for the async response 149 + } 150 + 151 + // For any other message types, we don't respond 152 + return false; 54 153 }); 55 154 56 - console.log("Service Worker started and listeners added (caching re-enabled)."); 155 + chrome.tabs.onUpdated.addListener(async (_, info, tab) => { 156 + if (info.status !== 'complete' || !tab.url) return; 157 + 158 + try { 159 + const data = await parseInput(tab.url); 160 + if (!data?.did || data.handle) return; 161 + 162 + // Check in-memory cache first 163 + const cached = cache.get(data.did); 164 + if (cached) { 165 + // Update lastAccessed in memory and schedule a save 166 + cached.lastAccessed = Date.now(); 167 + saveCache().catch(console.error); 168 + return; 169 + } 170 + 171 + // Not in cache, resolve and add to cache 172 + const h = await resolveDidToHandle(data.did); 173 + if (h) { 174 + await updateCache(data.did, h); 175 + } 176 + } catch (e) { 177 + console.error('SW error', e); 178 + } 179 + });
+79 -90
test.js
··· 1 - // Minimal test file for transform.js 2 - const { parseInput, resolveHandle } = require("./transform.js"); 3 - const assert = require("assert").strict; 1 + const { parseInput, resolveHandleToDid } = require('./transform.js'); 2 + const assert = require('assert').strict; 4 3 5 4 function mockFetchResponse(data = {}, isOk = true, httpStatus = 200) { 6 5 return () => ··· 13 12 14 13 const fetchMockConfigs = [ 15 14 { 16 - condition: (url) => url.includes("resolveHandle?handle=why.bsky.team"), 17 - response: mockFetchResponse({ did: "did:plc:vpkhqolt662uhesyj6nxm7ys" }), 15 + condition: (url) => url.includes('resolveHandle?handle=why.bsky.team'), 16 + response: mockFetchResponse({ did: 'did:plc:vpkhqolt662uhesyj6nxm7ys' }), 18 17 }, 19 18 { 20 - condition: (url) => url.includes("resolveHandle?handle=alice.mosphere.at"), 21 - response: mockFetchResponse({ did: "did:plc:by3jhwdqgbtrcc7q4tkkv3cf" }), 19 + condition: (url) => url.includes('resolveHandle?handle=alice.mosphere.at'), 20 + response: mockFetchResponse({ did: 'did:plc:by3jhwdqgbtrcc7q4tkkv3cf' }), 22 21 }, 23 22 { 24 - condition: (url) => url === "https://didweb.watch/.well-known/did.json", 23 + condition: (url) => url === 'https://didweb.watch/.well-known/did.json', 25 24 response: mockFetchResponse({ 26 - "@context": [ 27 - "https://www.w3.org/ns/did/v1", 28 - "https://w3id.org/security/multikey/v1", 29 - "https://w3id.org/security/suites/secp256k1-2019/v1", 25 + '@context': [ 26 + 'https://www.w3.org/ns/did/v1', 27 + 'https://w3id.org/security/multikey/v1', 28 + 'https://w3id.org/security/suites/secp256k1-2019/v1', 30 29 ], 31 - id: "did:web:didweb.watch", 32 - alsoKnownAs: ["at://didweb.watch"], 30 + id: 'did:web:didweb.watch', 31 + alsoKnownAs: ['at://didweb.watch'], 33 32 verificationMethod: [ 34 33 { 35 - id: "did:web:didweb.watch#atproto", 36 - type: "Multikey", 37 - controller: "did:web:didweb.watch", 38 - publicKeyMultibase: 39 - "zQ3shPLyZu2EbgJ75P61bMZP4yvBwmtd22ph5sEnY6oLz4YLo", 34 + id: 'did:web:didweb.watch#atproto', 35 + type: 'Multikey', 36 + controller: 'did:web:didweb.watch', 37 + publicKeyMultibase: 'zQ3shPLyZu2EbgJ75P61bMZP4yvBwmtd22ph5sEnY6oLz4YLo', 40 38 }, 41 39 ], 42 40 service: [ 43 41 { 44 - id: "#atproto_pds", 45 - type: "AtprotoPersonalDataServer", 46 - serviceEndpoint: "https://zio.blue", 42 + id: '#atproto_pds', 43 + type: 'AtprotoPersonalDataServer', 44 + serviceEndpoint: 'https://zio.blue', 47 45 }, 48 46 ], 49 47 }), 50 48 }, 51 49 { 52 - condition: (url) => url === "https://fail-did-web.com/.well-known/did.json", 50 + condition: (url) => url === 'https://fail-did-web.com/.well-known/did.json', 53 51 response: mockFetchResponse({}, false, 404), 54 52 }, 55 53 { 56 - condition: (url) => url.includes("resolveHandle?handle=bob.test"), 57 - response: mockFetchResponse({ did: "did:plc:bobtestdid" }), 54 + condition: (url) => url.includes('resolveHandle?handle=bob.test'), 55 + response: mockFetchResponse({ did: 'did:plc:bobtestdid' }), 58 56 }, 59 57 ]; 60 58 ··· 79 77 assert.deepStrictEqual(actual, expected); 80 78 console.log(`PASS: ${name}`); 81 79 passed++; 82 - } catch (err) { 83 - console.log(`FAIL: ${name}\nExpected:`, expected, "\nGot: ", actual); 84 - // Log the full error for debugging if needed, especially for assert failures 85 - // console.error(err); 80 + // eslint-disable-next-line no-unused-vars 81 + } catch (e) { 82 + console.log(`FAIL: ${name}\nExpected:`, expected, '\nGot: ', actual); 86 83 failed++; 87 84 } 88 85 } ··· 90 87 // Test cases for parseInput 91 88 const parseInputTests = [ 92 89 { 93 - name: "parseInput/feed/cozy", 94 - input: "https://deer.social/profile/why.bsky.team/feed/cozy", 90 + name: 'parseInput/feed/cozy', 91 + input: 'https://deer.social/profile/why.bsky.team/feed/cozy', 95 92 expected: { 96 - atUri: 97 - "at://did:plc:vpkhqolt662uhesyj6nxm7ys/app.bsky.feed.generator/cozy", 98 - did: "did:plc:vpkhqolt662uhesyj6nxm7ys", 99 - handle: "why.bsky.team", 100 - rkey: "cozy", 101 - nsid: "app.bsky.feed.generator", 102 - bskyAppPath: "/profile/why.bsky.team/feed/cozy", 93 + atUri: 'at://did:plc:vpkhqolt662uhesyj6nxm7ys/app.bsky.feed.generator/cozy', 94 + did: 'did:plc:vpkhqolt662uhesyj6nxm7ys', 95 + handle: 'why.bsky.team', 96 + rkey: 'cozy', 97 + nsid: 'app.bsky.feed.generator', 98 + bskyAppPath: '/profile/why.bsky.team/feed/cozy', 103 99 }, 104 100 }, 105 101 { 106 - name: "parseInput/feed.post", 107 - input: 108 - "https://deer.social/profile/did:plc:kkkcb7sys7623hcf7oefcffg/post/3lpe6ek6xhs2n", 102 + name: 'parseInput/feed.post', 103 + input: 'https://deer.social/profile/did:plc:kkkcb7sys7623hcf7oefcffg/post/3lpe6ek6xhs2n', 109 104 expected: { 110 - atUri: 111 - "at://did:plc:kkkcb7sys7623hcf7oefcffg/app.bsky.feed.post/3lpe6ek6xhs2n", 112 - did: "did:plc:kkkcb7sys7623hcf7oefcffg", 105 + atUri: 'at://did:plc:kkkcb7sys7623hcf7oefcffg/app.bsky.feed.post/3lpe6ek6xhs2n', 106 + did: 'did:plc:kkkcb7sys7623hcf7oefcffg', 113 107 handle: null, 114 - rkey: "3lpe6ek6xhs2n", 115 - nsid: "app.bsky.feed.post", 116 - bskyAppPath: 117 - "/profile/did:plc:kkkcb7sys7623hcf7oefcffg/post/3lpe6ek6xhs2n", 108 + rkey: '3lpe6ek6xhs2n', 109 + nsid: 'app.bsky.feed.post', 110 + bskyAppPath: '/profile/did:plc:kkkcb7sys7623hcf7oefcffg/post/3lpe6ek6xhs2n', 118 111 }, 119 112 }, 120 113 { 121 - name: "parseInput/lists", 122 - input: 123 - "https://deer.social/profile/alice.mosphere.at/lists/3l7vfhhfqcz2u", 114 + name: 'parseInput/lists', 115 + input: 'https://deer.social/profile/alice.mosphere.at/lists/3l7vfhhfqcz2u', 124 116 expected: { 125 - atUri: 126 - "at://did:plc:by3jhwdqgbtrcc7q4tkkv3cf/app.bsky.graph.list/3l7vfhhfqcz2u", 127 - did: "did:plc:by3jhwdqgbtrcc7q4tkkv3cf", 128 - handle: "alice.mosphere.at", 129 - rkey: "3l7vfhhfqcz2u", 130 - nsid: "app.bsky.graph.list", 131 - bskyAppPath: "/profile/alice.mosphere.at/lists/3l7vfhhfqcz2u", 117 + atUri: 'at://did:plc:by3jhwdqgbtrcc7q4tkkv3cf/app.bsky.graph.list/3l7vfhhfqcz2u', 118 + did: 'did:plc:by3jhwdqgbtrcc7q4tkkv3cf', 119 + handle: 'alice.mosphere.at', 120 + rkey: '3l7vfhhfqcz2u', 121 + nsid: 'app.bsky.graph.list', 122 + bskyAppPath: '/profile/alice.mosphere.at/lists/3l7vfhhfqcz2u', 132 123 }, 133 124 }, 134 125 { 135 - name: "parseInput/did:web", 136 - input: "https://deer.social/profile/did:web:didweb.watch", 126 + name: 'parseInput/did:web', 127 + input: 'https://deer.social/profile/did:web:didweb.watch', 137 128 expected: { 138 - atUri: "at://did:web:didweb.watch", 139 - did: "did:web:didweb.watch", 129 + atUri: 'at://did:web:didweb.watch', 130 + did: 'did:web:didweb.watch', 140 131 handle: null, 141 132 rkey: undefined, 142 133 nsid: undefined, 143 - bskyAppPath: "/profile/did:web:didweb.watch", 134 + bskyAppPath: '/profile/did:web:didweb.watch', 144 135 }, 145 136 }, 146 137 { 147 - name: "parseInput/did:web/post", 148 - input: 149 - "https://deer.social/profile/did:web:didweb.watch/post/3lpaioe62qk2j", 138 + name: 'parseInput/did:web/post', 139 + input: 'https://deer.social/profile/did:web:didweb.watch/post/3lpaioe62qk2j', 150 140 expected: { 151 - atUri: "at://did:web:didweb.watch/app.bsky.feed.post/3lpaioe62qk2j", 152 - did: "did:web:didweb.watch", 141 + atUri: 'at://did:web:didweb.watch/app.bsky.feed.post/3lpaioe62qk2j', 142 + did: 'did:web:didweb.watch', 153 143 handle: null, 154 - rkey: "3lpaioe62qk2j", 155 - nsid: "app.bsky.feed.post", 156 - bskyAppPath: "/profile/did:web:didweb.watch/post/3lpaioe62qk2j", 144 + rkey: '3lpaioe62qk2j', 145 + nsid: 'app.bsky.feed.post', 146 + bskyAppPath: '/profile/did:web:didweb.watch/post/3lpaioe62qk2j', 157 147 }, 158 148 }, 159 149 { 160 - name: "parseInput/queryParam/did", 161 - input: 162 - "https://boat.kelinci.net/plc-oplogs?q=did:plc:5sk4eqsu7byvwokfcnfgywxg", 150 + name: 'parseInput/queryParam/did', 151 + input: 'https://boat.kelinci.net/plc-oplogs?q=did:plc:5sk4eqsu7byvwokfcnfgywxg', 163 152 expected: { 164 - atUri: "at://did:plc:5sk4eqsu7byvwokfcnfgywxg", 165 - did: "did:plc:5sk4eqsu7byvwokfcnfgywxg", 153 + atUri: 'at://did:plc:5sk4eqsu7byvwokfcnfgywxg', 154 + did: 'did:plc:5sk4eqsu7byvwokfcnfgywxg', 166 155 handle: null, 167 156 nsid: undefined, 168 157 rkey: undefined, 169 - bskyAppPath: "/profile/did:plc:5sk4eqsu7byvwokfcnfgywxg", 158 + bskyAppPath: '/profile/did:plc:5sk4eqsu7byvwokfcnfgywxg', 170 159 }, 171 160 }, 172 161 ]; ··· 183 172 // Test cases for resolveHandle 184 173 const resolveHandleTests = [ 185 174 { 186 - name: "resolveHandle/plc/alice", 187 - input: "alice.mosphere.at", 188 - expected: "did:plc:by3jhwdqgbtrcc7q4tkkv3cf", 175 + name: 'resolveHandle/plc/alice', 176 + input: 'alice.mosphere.at', 177 + expected: 'did:plc:by3jhwdqgbtrcc7q4tkkv3cf', 189 178 }, 190 179 { 191 - name: "resolveHandle/plc/bob", 192 - input: "bob.test", 193 - expected: "did:plc:bobtestdid", 180 + name: 'resolveHandle/plc/bob', 181 + input: 'bob.test', 182 + expected: 'did:plc:bobtestdid', 194 183 }, 195 184 { 196 - name: "resolveHandle/did-web/success", 197 - input: "did:web:didweb.watch", 198 - expected: "did:web:didweb.watch", 185 + name: 'resolveHandle/did-web/success', 186 + input: 'did:web:didweb.watch', 187 + expected: 'did:web:didweb.watch', 199 188 }, 200 189 { 201 - name: "resolveHandle/did-web/failure", 202 - input: "did:web:fail-did-web.com", 203 - expected: "did:web:fail-did-web.com", 190 + name: 'resolveHandle/did-web/failure', 191 + input: 'did:web:fail-did-web.com', 192 + expected: 'did:web:fail-did-web.com', 204 193 }, 205 194 ]; 206 195 207 196 for (const test of resolveHandleTests) { 208 197 try { 209 - const out = await resolveHandle(test.input); 198 + const out = await resolveHandleToDid(test.input); 210 199 check(test.name, out, test.expected); 211 200 } catch (e) { 212 201 console.log(`FAIL: ${test.name} (exception)`, e);
+138 -305
transform.js
··· 1 1 const NSID_SHORTCUTS = { 2 - post: "app.bsky.feed.post", 3 - feed: "app.bsky.feed.generator", 4 - // list: "app.bsky.graph.list", 5 - lists: "app.bsky.graph.list", 2 + post: 'app.bsky.feed.post', 3 + feed: 'app.bsky.feed.generator', 4 + lists: 'app.bsky.graph.list', 6 5 }; 7 6 8 7 async function parseInput(raw) { 9 8 if (!raw) return null; 10 9 let str = decodeURIComponent(raw.trim()); 11 - if (!str.startsWith("http")) { 10 + if (!str.startsWith('http')) { 12 11 return await canonicalize(str); 13 12 } 14 13 15 - // Try to pull out at://... from inside web URL 16 - const atMatch = str.match(/at:\/\/[\w:.\-\/]+/); 14 + const atMatch = str.match(/at:\/\/[\w:.\-/]+/); 17 15 if (atMatch) { 18 16 return await canonicalize(atMatch[0]); 19 17 } 20 18 21 19 try { 22 20 const url = new URL(str); 23 - // Check for 'q' query parameter containing a DID 24 - const qParam = url.searchParams.get("q"); 25 - if (qParam && qParam.startsWith("did:")) { 21 + 22 + if (url.hostname === 'cred.blue' && url.pathname.length > 1) { 23 + const handle = url.pathname.slice(1); 24 + if (handle) { 25 + return await canonicalize(handle); 26 + } 27 + } 28 + 29 + const qParam = url.searchParams.get('q'); 30 + if (qParam && qParam.startsWith('did:')) { 26 31 return await canonicalize(qParam); 27 32 } 28 33 29 - // Existing logic to find DID/handle in path parts 30 34 const parts = str.split(/[/?#]/); 31 35 for (let i = 0; i < parts.length; i++) { 32 36 const p = parts[i]; 33 - if ( 34 - p.startsWith("did:") || 35 - (p.includes(".") && parts[i - 1] === "profile") 36 - ) { 37 - const rest = parts.slice(i + 1).join("/"); 38 - return await canonicalize(p + (rest ? "/" + rest : "")); 37 + if (p.startsWith('did:') || (p.includes('.') && parts[i - 1] === 'profile')) { 38 + const rest = parts.slice(i + 1).join('/'); 39 + return await canonicalize(p + (rest ? '/' + rest : '')); 39 40 } 40 41 } 41 - } catch (e) { 42 - // If URL parsing fails or other error, log it and fall through or return null 43 - console.error("Error parsing URL or its components:", e); 44 - // Depending on desired behavior, you might want to return null here 45 - // or let it fall through if there's a chance non-URL at:// strings are passed with http prefix 42 + } catch (error) { 43 + console.error('Error parsing input:', error); 46 44 } 47 45 48 46 return null; 49 47 } 50 48 51 49 async function canonicalize(fragment) { 52 - // Ensure at:// prefix 53 - let f = fragment.replace(/^at:\/([^/])/, "at://$1"); 54 - if (!f.startsWith("at://")) f = "at://" + f; 55 - // at://did/path or at://handle/path 56 - const [, idAndRest] = f.split("at://"); 57 - const [idPart, ...restParts] = idAndRest.split("/"); 58 - let did = idPart.startsWith("did:") ? idPart : null; 50 + let f = fragment.replace(/^at:\/([^/])/, 'at://$1'); 51 + if (!f.startsWith('at://')) f = 'at://' + f; 52 + const [, idAndRest] = f.split('at://'); 53 + const [idPart, ...restParts] = idAndRest.split('/'); 54 + let did = idPart.startsWith('did:') ? idPart : null; 59 55 const handle = did ? null : idPart; 60 56 if (!did && handle) { 61 - // Use the renamed function here 62 57 did = await resolveHandleToDid(handle); 63 58 } 64 59 ··· 68 63 restParts[0] = NSID_SHORTCUTS[first]; 69 64 } 70 65 } 71 - const pathRest = restParts.join("/"); 72 - const [nsid, rkey] = pathRest.split("/").filter(Boolean); 66 + const pathRest = restParts.join('/'); 67 + const [nsid, rkey] = pathRest.split('/').filter(Boolean); 73 68 74 - // --- bskyAppPath logic --- 75 - let bskyAppPath = ""; 69 + let bskyAppPath = ''; 76 70 const acct = handle || did; 77 71 if (acct) { 78 72 bskyAppPath = `/profile/${acct}`; 79 73 if (nsid && rkey) { 80 - // Find the shortcut key (e.g., "feed", "post") that matches the current full nsid 81 - const shortcutKey = Object.keys(NSID_SHORTCUTS).find( 82 - (key) => NSID_SHORTCUTS[key] === nsid 83 - ); 74 + const shortcutKey = Object.keys(NSID_SHORTCUTS).find((key) => NSID_SHORTCUTS[key] === nsid); 84 75 if (shortcutKey) { 85 76 bskyAppPath += `/${shortcutKey}/${rkey}`; 86 77 } 87 - // If nsid is not in NSID_SHORTCUTS (e.g. a custom nsid), 88 - // bskyAppPath remains /profile/acct, which is the current behavior. 89 78 } 90 79 } 91 80 92 - // Bail if did is falsy 93 81 if (!did) return null; 94 82 95 83 return { 96 - atUri: "at://" + (did || handle) + (pathRest ? "/" + pathRest : ""), 84 + atUri: 'at://' + (did || handle) + (pathRest ? '/' + pathRest : ''), 97 85 did, 98 86 handle, 99 87 rkey, ··· 102 90 }; 103 91 } 104 92 105 - // Renamed function: Takes a handle, returns a DID 106 93 async function resolveHandleToDid(handle) { 107 - // If handle is a did:web:... and looks like did:web:domain 108 - if (typeof handle === "string" && handle.startsWith("did:web:")) { 109 - const parts = handle.split(":"); 94 + if (typeof handle === 'string' && handle.startsWith('did:web:')) { 95 + const parts = handle.split(':'); 110 96 if (parts.length === 3) { 111 - // Try to fetch the full DID from .well-known/did.json 112 - const domain = parts[2]; 113 97 try { 114 - const resp = await fetch( 115 - "https://" + domain + "/.well-known/did.json" 116 - ).catch(() => null); 117 - if (resp && resp.ok) { 118 - const data = await resp.json(); 119 - return data.id || handle; 98 + const resp = await fetch(`https://${parts[2]}/.well-known/did.json`); 99 + if (resp?.ok) { 100 + const { id } = await resp.json(); 101 + return id || handle; 120 102 } 121 - } catch (_) { 122 - // fallback to returning the input 103 + } catch { 104 + /* ignore */ 123 105 } 124 106 return handle; 125 107 } 126 - // If it's a longer did:web:...:... just return as-is 127 108 return handle; 128 109 } 129 - // Otherwise, resolve handle via bsky API 130 110 try { 131 - const url = 132 - "https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=" + 133 - encodeURIComponent(handle); 134 - const resp = await fetch(url); 135 - if (!resp.ok) return null; 136 - const data = await resp.json(); 137 - return data.did || null; 138 - } catch (_) { 139 - return null; 111 + const resp = await fetch( 112 + `https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}`, 113 + ); 114 + if (resp.ok) { 115 + const { did: resolved } = await resp.json(); 116 + return resolved || null; 117 + } 118 + } catch { 119 + /* ignore */ 140 120 } 121 + return null; 141 122 } 142 123 143 - // Helper function to extract handle from alsoKnownAs array 144 124 function _extractHandleFromAlsoKnownAs(alsoKnownAs) { 145 125 if (Array.isArray(alsoKnownAs)) { 146 126 for (const aka of alsoKnownAs) { 147 - if (typeof aka === "string" && aka.startsWith("at://")) { 148 - const handle = aka.substring("at://".length); 127 + if (typeof aka === 'string' && aka.startsWith('at://')) { 128 + const handle = aka.substring('at://'.length); 149 129 if (handle) { 150 130 return handle; 151 131 } ··· 155 135 return null; 156 136 } 157 137 158 - // Helper function to construct the .well-known/did.json URL for did:web 159 138 function _getDidWebWellKnownUrl(did) { 160 - // Decode percent-encoded characters in the method-specific identifier part 161 - const methodSpecificId = decodeURIComponent( 162 - did.substring("did:web:".length).split("#")[0] 163 - ); 164 - const parts = methodSpecificId.split(":"); 165 - const hostAndPort = parts[0]; // e.g., "example.com" or "localhost:3000" 166 - let path = ""; 139 + const methodSpecificId = decodeURIComponent(did.substring('did:web:'.length).split('#')[0]); 140 + const parts = methodSpecificId.split(':'); 141 + const hostAndPort = parts[0]; 142 + let path = ''; 167 143 if (parts.length > 1) { 168 - // Path segments are joined by slashes 169 - path = "/" + parts.slice(1).join("/"); 144 + path = '/' + parts.slice(1).join('/'); 170 145 } 171 - // Ensure path doesn't end with a slash if it's not empty before appending /.well-known 172 - if (path && path.endsWith("/")) { 146 + if (path && path.endsWith('/')) { 173 147 path = path.slice(0, -1); 174 148 } 175 149 return `https://${hostAndPort}${path}/.well-known/did.json`; 176 150 } 177 151 178 - // New function: Takes a DID, returns a handle 179 152 async function resolveDidToHandle(did) { 180 - if (!did || typeof did !== "string") { 181 - console.warn( 182 - "resolveDidToHandle: Invalid DID provided (null or not a string)", 183 - did 184 - ); 185 - return null; 186 - } 187 - 188 - // --- DID:PLC --- 189 - if (did.startsWith("did:plc:")) { 190 - console.log(`resolveDidToHandle: Processing did:plc: ${did}`); 191 - // Primary: plc.directory 153 + if (!did || typeof did !== 'string') return null; 154 + if (did.startsWith('did:plc:')) { 192 155 try { 193 - const plcUrl = `https://plc.directory/${encodeURIComponent(did)}`; 194 - console.log("resolveDidToHandle: Fetching from PLC directory:", plcUrl); 195 - const plcResp = await fetch(plcUrl); 196 - if (plcResp.ok) { 197 - const plcData = await plcResp.json(); 198 - console.log( 199 - "resolveDidToHandle: PLC directory success for", 200 - did, 201 - "; Data snippet:", 202 - JSON.stringify(plcData).substring(0, 200) + "..." 203 - ); 204 - if (plcData.alsoKnownAs) { 205 - const handleFromPlc = _extractHandleFromAlsoKnownAs( 206 - plcData.alsoKnownAs 207 - ); 208 - if (handleFromPlc) { 209 - console.log( 210 - `resolveDidToHandle: Found handle '${handleFromPlc}' from plc.directory alsoKnownAs for ${did}` 211 - ); 212 - return handleFromPlc; 213 - } 214 - } 215 - } else { 216 - const errorText = await plcResp 217 - .text() 218 - .catch(() => "Failed to get error text from plcResp"); 219 - console.warn( 220 - `resolveDidToHandle: plc.directory fetch failed for ${did} with status ${plcResp.status}. Error: ${errorText}` 221 - ); 156 + const resp = await fetch(`https://plc.directory/${encodeURIComponent(did)}`); 157 + if (resp.ok) { 158 + const { alsoKnownAs } = await resp.json(); 159 + const h = _extractHandleFromAlsoKnownAs(alsoKnownAs); 160 + if (h) return h; 222 161 } 223 - } catch (error) { 224 - console.error( 225 - `resolveDidToHandle: Error fetching/parsing PLC directory for ${did}:`, 226 - error 227 - ); 162 + } catch { 163 + /* ignore */ 228 164 } 229 - 230 - // Fallback to describeRepo for did:plc: 231 - console.log( 232 - `resolveDidToHandle: Falling back to describeRepo for did:plc: ${did}` 233 - ); 234 165 try { 235 - const drUrl = `https://public.api.bsky.app/xrpc/com.atproto.repo.describeRepo?repo=${encodeURIComponent( 236 - did 237 - )}`; 238 - console.log( 239 - "resolveDidToHandle: Attempting describeRepo fetch (plc fallback):", 240 - drUrl 166 + const resp = await fetch( 167 + `https://public.api.bsky.app/xrpc/com.atproto.repo.describeRepo?repo=${encodeURIComponent(did)}`, 241 168 ); 242 - const drResp = await fetch(drUrl); 243 - if (drResp.ok) { 244 - const drData = await drResp.json(); 245 - console.log( 246 - "resolveDidToHandle: describeRepo success for", 247 - did, 248 - "(plc fallback); Data:", 249 - JSON.stringify(drData) 250 - ); 251 - if (drData.handle) { 252 - console.log( 253 - `resolveDidToHandle: Found handle '${drData.handle}' from describeRepo (plc fallback) for ${did}` 254 - ); 255 - return drData.handle; 256 - } 169 + if (resp.ok) { 170 + const { handle } = await resp.json(); 171 + if (handle) return handle; 257 172 } 258 - } catch (error) { 259 - console.error( 260 - `resolveDidToHandle: Error in describeRepo (plc fallback) for ${did}:`, 261 - error 262 - ); 173 + } catch { 174 + /* ignore */ 263 175 } 264 - 265 - console.log( 266 - `resolveDidToHandle: All resolution methods failed for did:plc: ${did}` 267 - ); 268 176 return null; 269 177 } 270 - 271 - // --- DID:WEB --- 272 - else if (did.startsWith("did:web:")) { 273 - console.log(`resolveDidToHandle: Processing did:web: ${did}`); 274 - const methodSpecificId = decodeURIComponent( 275 - did.substring("did:web:".length).split("#")[0] 276 - ); 277 - 278 - // Primary: .well-known/did.json 178 + if (did.startsWith('did:web:')) { 179 + const url = _getDidWebWellKnownUrl(did); 279 180 try { 280 - const wellKnownUrl = _getDidWebWellKnownUrl(did); 281 - console.log( 282 - "resolveDidToHandle: Fetching from .well-known/did.json:", 283 - wellKnownUrl 284 - ); 285 - const wkResp = await fetch(wellKnownUrl); 286 - if (wkResp.ok) { 287 - const wkData = await wkResp.json(); 288 - console.log( 289 - "resolveDidToHandle: .well-known success for", 290 - did, 291 - "; Data snippet:", 292 - JSON.stringify(wkData).substring(0, 200) + "..." 293 - ); 294 - if (wkData.alsoKnownAs) { 295 - const handleFromWk = _extractHandleFromAlsoKnownAs( 296 - wkData.alsoKnownAs 297 - ); 298 - if (handleFromWk) { 299 - console.log( 300 - `resolveDidToHandle: Found handle '${handleFromWk}' from .well-known alsoKnownAs for ${did}` 301 - ); 302 - return handleFromWk; 303 - } 304 - } 305 - } else { 306 - const errorText = await wkResp 307 - .text() 308 - .catch(() => "Failed to get error text from wkResp"); 309 - console.warn( 310 - `resolveDidToHandle: .well-known fetch failed for ${did} (url: ${wellKnownUrl}) status ${wkResp.status}. Error: ${errorText}` 311 - ); 181 + const resp = await fetch(url); 182 + if (resp.ok) { 183 + const { alsoKnownAs } = await resp.json(); 184 + const h = _extractHandleFromAlsoKnownAs(alsoKnownAs); 185 + if (h) return h; 312 186 } 313 - } catch (error) { 314 - console.error( 315 - `resolveDidToHandle: Error fetching/parsing .well-known/did.json for ${did}:`, 316 - error 317 - ); 187 + } catch { 188 + /* ignore */ 318 189 } 319 - 320 - // Fallback 1: describeRepo 321 - console.log( 322 - `resolveDidToHandle: Falling back to describeRepo for did:web: ${did}` 323 - ); 324 190 try { 325 - const drUrl = `https://public.api.bsky.app/xrpc/com.atproto.repo.describeRepo?repo=${encodeURIComponent( 326 - did 327 - )}`; 328 - console.log( 329 - "resolveDidToHandle: Attempting describeRepo fetch (web fallback 1):", 330 - drUrl 191 + const resp = await fetch( 192 + `https://public.api.bsky.app/xrpc/com.atproto.repo.describeRepo?repo=${encodeURIComponent(did)}`, 331 193 ); 332 - const drResp = await fetch(drUrl); 333 - if (drResp.ok) { 334 - const drData = await drResp.json(); 335 - console.log( 336 - "resolveDidToHandle: describeRepo success for", 337 - did, 338 - "(web fallback 1); Data:", 339 - JSON.stringify(drData) 340 - ); 341 - if (drData.handle) { 342 - console.log( 343 - `resolveDidToHandle: Found handle '${drData.handle}' from describeRepo (web fallback 1) for ${did}` 344 - ); 345 - return drData.handle; 346 - } 194 + if (resp.ok) { 195 + const { handle } = await resp.json(); 196 + if (handle) return handle; 347 197 } 348 - } catch (error) { 349 - console.error( 350 - `resolveDidToHandle: Error in describeRepo (web fallback 1) for ${did}:`, 351 - error 352 - ); 198 + } catch { 199 + /* ignore */ 353 200 } 354 - 355 - // Fallback 2: Derive from did:web string (methodSpecificId) 356 - console.log( 357 - `resolveDidToHandle: All other methods failed for did:web: ${did}. Falling back to derived handle: ${methodSpecificId}` 358 - ); 359 - return methodSpecificId; 201 + return decodeURIComponent(did.substring('did:web:'.length).split('#')[0]); 360 202 } 361 - 362 - // --- UNSUPPORTED DID METHOD --- 363 - else { 364 - console.warn( 365 - `resolveDidToHandle: Unsupported DID method for ${did}. Only did:plc and did:web are supported.` 366 - ); 367 - return null; 368 - } 203 + return null; 369 204 } 370 205 371 206 function buildDestinations(info) { 372 - // Use the new bskyAppPath for both deer.social and bsky.app 373 207 const { atUri, did, handle, rkey, bskyAppPath } = info; 374 - const isDidWeb = did && did.startsWith("did:web:"); 208 + const isDidWeb = did && did.startsWith('did:web:'); 375 209 return [ 376 - { label: "🦌 deer.social", url: `https://deer.social${bskyAppPath}` }, 377 - { label: "🦋 bsky.app", url: `https://bsky.app${bskyAppPath}` }, 210 + { label: '🦌 deer.social', url: `https://deer.social${bskyAppPath}` }, 211 + { label: '🦋 bsky.app', url: `https://bsky.app${bskyAppPath}` }, 378 212 { 379 - label: "⚙️ pdsls.dev", 213 + label: '⚙️ pdsls.dev', 380 214 url: `https://pdsls.dev/${atUri}`, 381 215 }, 382 216 { 383 - label: "🛠️ atp.tools", 217 + label: '🛠️ atp.tools', 384 218 url: `https://atp.tools/${atUri}`, 385 219 }, 386 220 { 387 - label: "☀️ clearsky", 221 + label: '☀️ clearsky', 388 222 url: `https://clearsky.app/${did}/blocked-by`, 389 223 }, 390 - ...(rkey 391 - ? [ 392 - { 393 - label: "☁️ skythread", 394 - url: `https://blue.mackuba.eu/skythread/?author=${did}&post=${rkey}`, 395 - }, 396 - ] 397 - : []), 398 - ...(handle 399 - ? [ 400 - { 401 - label: "🍥 cred.blue", 402 - url: `https://cred.blue/${handle}`, 403 - }, 404 - { 405 - label: "🪢 tangled.sh", 406 - url: `https://tangled.sh/@${handle}`, 407 - }, 408 - { 409 - label: "📰 frontpage.fyi", 410 - url: `https://frontpage.fyi/profile/${handle}`, 411 - }, 412 - ] 413 - : []), 414 - ...(!isDidWeb 415 - ? [ 416 - { 417 - label: "⛵ boat.kelinci", 418 - url: `https://boat.kelinci.net/plc-oplogs?q=${did}`, 419 - }, 420 - { 421 - label: "🪪 plc.directory", 422 - url: `https://plc.directory/${did}`, 423 - }, 424 - ] 425 - : []), 224 + ...(rkey ? 225 + [ 226 + { 227 + label: '☁️ skythread', 228 + url: `https://blue.mackuba.eu/skythread/?author=${did}&post=${rkey}`, 229 + }, 230 + ] 231 + : []), 232 + ...(handle ? 233 + [ 234 + { 235 + label: '🍥 cred.blue', 236 + url: `https://cred.blue/${handle}`, 237 + }, 238 + { 239 + label: '🪢 tangled.sh', 240 + url: `https://tangled.sh/@${handle}`, 241 + }, 242 + { 243 + label: '📰 frontpage.fyi', 244 + url: `https://frontpage.fyi/profile/${handle}`, 245 + }, 246 + ] 247 + : []), 248 + ...(!isDidWeb ? 249 + [ 250 + { 251 + label: '⛵ boat.kelinci', 252 + url: `https://boat.kelinci.net/plc-oplogs?q=${did}`, 253 + }, 254 + { 255 + label: '🪪 plc.directory', 256 + url: `https://plc.directory/${did}`, 257 + }, 258 + ] 259 + : []), 426 260 ].filter((d) => !!d && !!d.url); 427 261 } 428 262 429 - // Node.js (test) and browser (extension) export compatibility 430 - if (typeof module !== "undefined" && module.exports) { 263 + if (typeof module !== 'undefined' && module.exports) { 431 264 module.exports = { 432 265 NSID_SHORTCUTS, 433 266 parseInput, 434 267 canonicalize, 435 - resolveHandleToDid, // Export with new name 436 - resolveDidToHandle, // Export new function 268 + resolveHandleToDid, 269 + resolveDidToHandle, 437 270 buildDestinations, 438 271 }; 439 - } else if (typeof window !== "undefined") { 272 + } else if (typeof window !== 'undefined') { 440 273 window.WormholeTransform = { 441 274 parseInput, 442 275 canonicalize, 443 - resolveHandleToDid, // Export with new name for window object 444 - resolveDidToHandle, // Export new function for window object 276 + resolveHandleToDid, 277 + resolveDidToHandle, 445 278 buildDestinations, 446 279 }; 447 280 }