Added write your own SDK guide
This commit is contained in:
@@ -12,6 +12,7 @@
|
|||||||
"@orama/orama": "^3.1.18",
|
"@orama/orama": "^3.1.18",
|
||||||
"@typescript/ata": "^0.9.8",
|
"@typescript/ata": "^0.9.8",
|
||||||
"@typescript/vfs": "^1.6.4",
|
"@typescript/vfs": "^1.6.4",
|
||||||
|
"beautiful-mermaid": "^1.1.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"cnfast": "^0.0.8",
|
"cnfast": "^0.0.8",
|
||||||
"fumadocs-core": "16.12.1",
|
"fumadocs-core": "16.12.1",
|
||||||
@@ -19,7 +20,8 @@
|
|||||||
"fumadocs-twoslash": "^3.3.0",
|
"fumadocs-twoslash": "^3.3.0",
|
||||||
"fumadocs-ui": "npm:@fumadocs/[email protected]",
|
"fumadocs-ui": "npm:@fumadocs/[email protected]",
|
||||||
"lucide-react": "^1.3.0",
|
"lucide-react": "^1.3.0",
|
||||||
"next": "^16.2.12",
|
"next": "^16.3.4",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.2.8",
|
"react": "^19.2.8",
|
||||||
"react-dom": "^19.2.8",
|
"react-dom": "^19.2.8",
|
||||||
"takumi-js": "^2.4.0",
|
"takumi-js": "^2.4.0",
|
||||||
@@ -27,7 +29,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^2.5.5",
|
"@biomejs/biome": "^2.5.5",
|
||||||
"@duckity/js": "^0.1.4",
|
|
||||||
"@tailwindcss/postcss": "^4.3.3",
|
"@tailwindcss/postcss": "^4.3.3",
|
||||||
"@types/mdx": "^2.0.14",
|
"@types/mdx": "^2.0.14",
|
||||||
"@types/node": "^26.1.1",
|
"@types/node": "^26.1.1",
|
||||||
@@ -68,10 +69,6 @@
|
|||||||
|
|
||||||
"@biomejs/cli-win32-x64": ["@biomejs/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-bIBFo+n6MIxdNcVFy5CrurbKiZQiUciK3bt8+O9I4wjFZNTfXLpi+giq47522eXqW5NBc9ulx7dR1SlZKi2J5g=="],
|
"@biomejs/cli-win32-x64": ["@biomejs/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-bIBFo+n6MIxdNcVFy5CrurbKiZQiUciK3bt8+O9I4wjFZNTfXLpi+giq47522eXqW5NBc9ulx7dR1SlZKi2J5g=="],
|
||||||
|
|
||||||
"@duckity/js": ["@duckity/[email protected]", "", { "dependencies": { "@duckity/wasm": "1.1.16" }, "peerDependencies": { "typescript": "^5" } }, "sha512-zjzV2vpKdrFwt954x5thbocTAflrNUVj7yTqMUC8E0ICAbJ9YP30arZvGZqTE98+vemxOIb9hH4SWja7BXH37w=="],
|
|
||||||
|
|
||||||
"@duckity/wasm": ["@duckity/[email protected]", "", {}, "sha512-P0TDKQ187S68D3ri2ZW9m60gCnUj+/iDLgwWwJLAbfiJhxEFfsCb7tJDgaQQR9mJ2PRETuBHc4TjbLphSuAjGg=="],
|
|
||||||
|
|
||||||
"@emnapi/runtime": ["@emnapi/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="],
|
"@emnapi/runtime": ["@emnapi/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="],
|
||||||
|
|
||||||
"@esbuild/aix-ppc64": ["@esbuild/[email protected]", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
|
"@esbuild/aix-ppc64": ["@esbuild/[email protected]", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
|
||||||
@@ -144,53 +141,57 @@
|
|||||||
|
|
||||||
"@img/colour": ["@img/[email protected]", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="],
|
"@img/colour": ["@img/[email protected]", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="],
|
||||||
|
|
||||||
"@img/sharp-darwin-arm64": ["@img/[email protected]4.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
|
"@img/sharp-darwin-arm64": ["@img/[email protected]5.4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.3.3" }, "os": "darwin", "cpu": "arm64" }, "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA=="],
|
||||||
|
|
||||||
"@img/sharp-darwin-x64": ["@img/[email protected]4.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="],
|
"@img/sharp-darwin-x64": ["@img/[email protected]5.4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.3.3" }, "os": "darwin", "cpu": "x64" }, "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw=="],
|
||||||
|
|
||||||
"@img/sharp-libvips-darwin-arm64": ["@img/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="],
|
"@img/sharp-freebsd-wasm32": ["@img/[email protected]", "", { "dependencies": { "@img/sharp-wasm32": "0.35.4" }, "os": "freebsd" }, "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA=="],
|
||||||
|
|
||||||
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="],
|
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.3.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg=="],
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="],
|
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw=="],
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="],
|
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.3.3", "", { "os": "linux", "cpu": "arm" }, "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA=="],
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="],
|
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A=="],
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="],
|
"@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.3.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w=="],
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-[email protected]", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="],
|
"@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ=="],
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="],
|
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-[email protected]", "", { "os": "linux", "cpu": "s390x" }, "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q=="],
|
||||||
|
|
||||||
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="],
|
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA=="],
|
||||||
|
|
||||||
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="],
|
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw=="],
|
||||||
|
|
||||||
"@img/sharp-linux-arm": ["@img/[email protected]", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="],
|
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linux[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw=="],
|
||||||
|
|
||||||
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="],
|
"@img/sharp-linux-arm": ["@img/[email protected]5.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.3.3" }, "os": "linux", "cpu": "arm" }, "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A=="],
|
||||||
|
|
||||||
"@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc[email protected]4.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="],
|
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm[email protected]5.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.3.3" }, "os": "linux", "cpu": "arm64" }, "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ=="],
|
||||||
|
|
||||||
"@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv[email protected]4.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="],
|
"@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc[email protected]5.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.3.3" }, "os": "linux", "cpu": "ppc64" }, "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ=="],
|
||||||
|
|
||||||
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="],
|
"@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.3.3" }, "os": "linux", "cpu": "none" }, "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA=="],
|
||||||
|
|
||||||
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="],
|
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.3.3" }, "os": "linux", "cpu": "s390x" }, "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ=="],
|
||||||
|
|
||||||
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm[email protected]4.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="],
|
"@img/sharp-linux-x64": ["@img/sharp-linux-x[email protected]5.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.3.3" }, "os": "linux", "cpu": "x64" }, "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA=="],
|
||||||
|
|
||||||
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x[email protected]4.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="],
|
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm[email protected]5.4", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.3.3" }, "os": "linux", "cpu": "arm64" }, "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA=="],
|
||||||
|
|
||||||
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="],
|
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.3.3" }, "os": "linux", "cpu": "x64" }, "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg=="],
|
||||||
|
|
||||||
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="],
|
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.35.4", "", { "dependencies": { "@emnapi/runtime": "^1.11.3" } }, "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA=="],
|
||||||
|
|
||||||
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia[email protected]4.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="],
|
"@img/sharp-webcontainers-wasm32": ["@img/sharp-webcontainers-wasm[email protected]5.4", "", { "dependencies": { "@img/sharp-wasm32": "0.35.4" }, "cpu": "none" }, "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA=="],
|
||||||
|
|
||||||
"@img/sharp-win32-x64": ["@img/sharp-win32-x[email protected]4.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
|
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm[email protected]5.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw=="],
|
||||||
|
|
||||||
|
"@img/sharp-win32-ia32": ["@img/[email protected]", "", { "os": "win32", "cpu": "ia32" }, "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw=="],
|
||||||
|
|
||||||
|
"@img/sharp-win32-x64": ["@img/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg=="],
|
||||||
|
|
||||||
"@jridgewell/gen-mapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
"@jridgewell/gen-mapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||||
|
|
||||||
@@ -204,23 +205,23 @@
|
|||||||
|
|
||||||
"@mdx-js/mdx": ["@mdx-js/[email protected]", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="],
|
"@mdx-js/mdx": ["@mdx-js/[email protected]", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="],
|
||||||
|
|
||||||
"@next/env": ["@next/env@16.2.12", "", {}, "sha512-d0Z5Bc13Fa4nR8pFAKx2jay2yhJM16vlfHbTzYnUQAxlNb6B6lmn4hjt69lYNt4kRtyYP6gEM49lPRHNbIyneg=="],
|
"@next/env": ["@next/env@16.3.4", "", {}, "sha512-cjWZnUUa6jZq2kFaNe/ZyJdZonOZ/QoN0Zka2nz/FLOrfx14pQuM9c5RaSVkWMqgdt4ksgPAMWPyHSs/CyV48Q=="],
|
||||||
|
|
||||||
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0W1R0teHWJrqKX0FH20IzzIWAOuGtBxPGuObrxy1lE8hQvCFj49KE8a3WUg0D7sq6rn6zkM4c7YGUnhudBS6oA=="],
|
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.3.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iBr3I5LZNk5/bgl5//iTgD2tcym14MX0Xo7fD//u9dYAEgGzza1y9oywluPtf74YnOswVdH1908aK9xVz7zQTw=="],
|
||||||
|
|
||||||
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.2.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-Hy5Ls099+aFUmOLmIgPfLqNi6iCwhL3uQCssz5rWk+5Nkc6TUKCE83DY5BbNylfm3+mfwcSFnLRfrZDJhVxdtw=="],
|
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.3.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-2dpiSyl2Jw/NrBPaU2MAKGSa+2MR82pJIn4Sm5Rjr+gxAeuh0z158Su3Z2O8zn7UNNq+ej4bToed6RcRN/Lydg=="],
|
||||||
|
|
||||||
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.2.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-+YqU2h1cQkHsGfvjAsrSmst8UIFBibBGm5x3Xgel8NLMiDQtNOM4sM2GOEMvG5YiOBNeN/Ykk8cQC2S0Xrqljg=="],
|
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+t+U8HZT+fApePCS5h89CSH3datz29MkzyfCn+6fpsZBG/oiEOhINcb9rtkv6sdpToLGFn2e6146NzaKCXkqrA=="],
|
||||||
|
|
||||||
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.2.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-0qjhiYBaKAqF63LA1ZWAAnKTzFUguAaZiRa5etMLGGPj/B6uEVjtIZldIzFEp3wHlB0koK6aTzqPtSdplTCjoA=="],
|
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-mx03GNs1ocQA5JQ4FxDMmIsNkdrZh8cuezKCrId28e5/gIPU/l7Kcy2+vmCCzdjnnmXJy+iOAu+7K0QppO6Urg=="],
|
||||||
|
|
||||||
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.2.12", "", { "os": "linux", "cpu": "x64" }, "sha512-7A3q26W+h7gnA15uqBToNuDqBEFZZcqh0mW2mn4AJh/G5pdg2RVE3n4slzLEliASZFG3NmsbEzng/x2Sh09mBg=="],
|
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-YIhGY6fSMfha52bnVxnzc9zaVBzJg+cqQTOD8tXIBSx4fuv0pVMxQTE0PaS59YhnMOiYiG09IMwxJAf/CFm/Dw=="],
|
||||||
|
|
||||||
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.2.12", "", { "os": "linux", "cpu": "x64" }, "sha512-qSjL/uppm+cbh21s72Ss8gkiOhQ4dExWHNGOWy6eZV7STj5WsKehgxT61beSsOj+YYQuTplL376lOCdMQU5T8w=="],
|
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+eaaX6axpDb0yF1GCpiERe6njplvdC+nks/fKfcHu3XPGRrald8P3/X7yv7QLdjA51knnxwl9pxdIJsg+w1L+Q=="],
|
||||||
|
|
||||||
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.2.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-X6hzsOUJac/e7AWSbn9gQ9nzHld1xWP5iyjHpYWvud8pufB679O1xg4JDyKr8Xd69Jvd+kM2Der6uftiZCmjYA=="],
|
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.3.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-0jcXW7Xs/uzICrmgV3MhDYDeRy++1CqnpDIerlPIqYO4bhzB4WNbX/aRnQclustsAyTkFKB0z6rbcjmNg5tR8A=="],
|
||||||
|
|
||||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.12", "", { "os": "win32", "cpu": "x64" }, "sha512-F6fakeHuFTLOPt0bslQJdf+xtT+WIP9DVn/m4y1w1mRnVPyh3D/cNvzlRkxM444xfm+IvvYNSOrKiA2CDJ0Uxw=="],
|
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.3.4", "", { "os": "win32", "cpu": "x64" }, "sha512-vvBzwu1pYQCp92maZCFCIw/XgOTMR5tur9GjakwIo2cmwRTMKajRZZDS9+e4KsUZWKu1E007WUeAFXRRjZeuzw=="],
|
||||||
|
|
||||||
"@orama/orama": ["@orama/[email protected]", "", {}, "sha512-a61ljmRVVyG5MC/698C8/FfFDw5a8LOIvyOLW5fztgUXqUpc1jOfQzOitSCbge657OgXXThmY3Tk8fpiDb4UcA=="],
|
"@orama/orama": ["@orama/[email protected]", "", {}, "sha512-a61ljmRVVyG5MC/698C8/FfFDw5a8LOIvyOLW5fztgUXqUpc1jOfQzOitSCbge657OgXXThmY3Tk8fpiDb4UcA=="],
|
||||||
|
|
||||||
@@ -244,7 +245,7 @@
|
|||||||
|
|
||||||
"@standard-schema/spec": ["@standard-schema/[email protected]", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
"@standard-schema/spec": ["@standard-schema/[email protected]", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
"@swc/helpers": ["@swc/[email protected].15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
|
"@swc/helpers": ["@swc/[email protected].23", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw=="],
|
||||||
|
|
||||||
"@tailwindcss/node": ["@tailwindcss/[email protected]", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.24.1", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.3" } }, "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg=="],
|
"@tailwindcss/node": ["@tailwindcss/[email protected]", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.24.1", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.3" } }, "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg=="],
|
||||||
|
|
||||||
@@ -376,6 +377,8 @@
|
|||||||
|
|
||||||
"baseline-browser-mapping": ["[email protected]", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-sbT0Ui/CZwyAyy7icT1Gw5P1LKRlFaHwaF6tDCW5YHq2X5SeeZFphBuIagopSfwSSZq3sQcbmEL072yphxm7ew=="],
|
"baseline-browser-mapping": ["[email protected]", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-sbT0Ui/CZwyAyy7icT1Gw5P1LKRlFaHwaF6tDCW5YHq2X5SeeZFphBuIagopSfwSSZq3sQcbmEL072yphxm7ew=="],
|
||||||
|
|
||||||
|
"beautiful-mermaid": ["[email protected]", "", { "dependencies": { "elkjs": "^0.11.0", "entities": "^7.0.1" } }, "sha512-TItrtrAyHp1vwFfFVYauWGrquouk/6SS21Aq3RsxindSYZODcN4xYrPZD6BiZRU+o5mKJzDPz9MUSMvELdylyg=="],
|
||||||
|
|
||||||
"boxen": ["[email protected]", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^7.0.0", "chalk": "^5.0.1", "cli-boxes": "^3.0.0", "string-width": "^5.1.2", "type-fest": "^2.13.0", "widest-line": "^4.0.1", "wrap-ansi": "^8.0.1" } }, "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg=="],
|
"boxen": ["[email protected]", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^7.0.0", "chalk": "^5.0.1", "cli-boxes": "^3.0.0", "string-width": "^5.1.2", "type-fest": "^2.13.0", "widest-line": "^4.0.1", "wrap-ansi": "^8.0.1" } }, "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg=="],
|
||||||
|
|
||||||
"brace-expansion": ["[email protected]", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw=="],
|
"brace-expansion": ["[email protected]", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw=="],
|
||||||
@@ -452,11 +455,13 @@
|
|||||||
|
|
||||||
"eastasianwidth": ["[email protected]", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
"eastasianwidth": ["[email protected]", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
||||||
|
|
||||||
|
"elkjs": ["[email protected]", "", {}, "sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg=="],
|
||||||
|
|
||||||
"emoji-regex": ["[email protected]", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
"emoji-regex": ["[email protected]", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||||
|
|
||||||
"enhanced-resolve": ["[email protected]", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ=="],
|
"enhanced-resolve": ["[email protected]", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ=="],
|
||||||
|
|
||||||
"entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
"entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
|
||||||
|
|
||||||
"esast-util-from-estree": ["[email protected]", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "unist-util-position-from-estree": "^2.0.0" } }, "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ=="],
|
"esast-util-from-estree": ["[email protected]", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "unist-util-position-from-estree": "^2.0.0" } }, "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ=="],
|
||||||
|
|
||||||
@@ -724,7 +729,7 @@
|
|||||||
|
|
||||||
"negotiator": ["[email protected]", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="],
|
"negotiator": ["[email protected]", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="],
|
||||||
|
|
||||||
"next": ["next@16.2.12", "", { "dependencies": { "@next/env": "16.2.12", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.12", "@next/swc-darwin-x64": "16.2.12", "@next/swc-linux-arm64-gnu": "16.2.12", "@next/swc-linux-arm64-musl": "16.2.12", "@next/swc-linux-x64-gnu": "16.2.12", "@next/swc-linux-x64-musl": "16.2.12", "@next/swc-win32-arm64-msvc": "16.2.12", "@next/swc-win32-x64-msvc": "16.2.12", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-iD59eYQWmbFcEbX7v/acG5DRym9iw1DdaPoD0WTA920naWsE25wShzJW4+UvAs8MK9EC2kBfIH6vtto1H1PHGw=="],
|
"next": ["next@16.3.4", "", { "dependencies": { "@next/env": "16.3.4", "@swc/helpers": "0.5.23", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.5.23", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.3.4", "@next/swc-darwin-x64": "16.3.4", "@next/swc-linux-arm64-gnu": "16.3.4", "@next/swc-linux-arm64-musl": "16.3.4", "@next/swc-linux-x64-gnu": "16.3.4", "@next/swc-linux-x64-musl": "16.3.4", "@next/swc-win32-arm64-msvc": "16.3.4", "@next/swc-win32-x64-msvc": "16.3.4", "sharp": "^0.35.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-/Ztf6CeRH+ejEXUrYtqI4gkS66eFIHuSwqi60RgcpWKodxFZx2/dqVCMKBwILfAHXQ+F1b1vAudgj3mnxqtoIA=="],
|
||||||
|
|
||||||
"next-themes": ["[email protected]", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
|
"next-themes": ["[email protected]", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
|
||||||
|
|
||||||
@@ -826,7 +831,7 @@
|
|||||||
|
|
||||||
"serve-handler": ["[email protected]", "", { "dependencies": { "bytes": "3.0.0", "content-disposition": "0.5.2", "mime-types": "2.1.18", "minimatch": "3.1.5", "path-is-inside": "1.0.2", "path-to-regexp": "3.3.0", "range-parser": "1.2.0" } }, "sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg=="],
|
"serve-handler": ["[email protected]", "", { "dependencies": { "bytes": "3.0.0", "content-disposition": "0.5.2", "mime-types": "2.1.18", "minimatch": "3.1.5", "path-is-inside": "1.0.2", "path-to-regexp": "3.3.0", "range-parser": "1.2.0" } }, "sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg=="],
|
||||||
|
|
||||||
"sharp": ["[email protected]4.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
|
"sharp": ["[email protected]5.4", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.4", "@img/sharp-darwin-x64": "0.35.4", "@img/sharp-freebsd-wasm32": "0.35.4", "@img/sharp-libvips-darwin-arm64": "1.3.3", "@img/sharp-libvips-darwin-x64": "1.3.3", "@img/sharp-libvips-linux-arm": "1.3.3", "@img/sharp-libvips-linux-arm64": "1.3.3", "@img/sharp-libvips-linux-ppc64": "1.3.3", "@img/sharp-libvips-linux-riscv64": "1.3.3", "@img/sharp-libvips-linux-s390x": "1.3.3", "@img/sharp-libvips-linux-x64": "1.3.3", "@img/sharp-libvips-linuxmusl-arm64": "1.3.3", "@img/sharp-libvips-linuxmusl-x64": "1.3.3", "@img/sharp-linux-arm": "0.35.4", "@img/sharp-linux-arm64": "0.35.4", "@img/sharp-linux-ppc64": "0.35.4", "@img/sharp-linux-riscv64": "0.35.4", "@img/sharp-linux-s390x": "0.35.4", "@img/sharp-linux-x64": "0.35.4", "@img/sharp-linuxmusl-arm64": "0.35.4", "@img/sharp-linuxmusl-x64": "0.35.4", "@img/sharp-webcontainers-wasm32": "0.35.4", "@img/sharp-win32-arm64": "0.35.4", "@img/sharp-win32-ia32": "0.35.4", "@img/sharp-win32-x64": "0.35.4" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA=="],
|
||||||
|
|
||||||
"shebang-command": ["[email protected]", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
"shebang-command": ["[email protected]", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||||
|
|
||||||
@@ -960,10 +965,10 @@
|
|||||||
|
|
||||||
"mime-types/mime-db": ["[email protected]", "", {}, "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ=="],
|
"mime-types/mime-db": ["[email protected]", "", {}, "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ=="],
|
||||||
|
|
||||||
"next/postcss": ["[email protected]", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
|
|
||||||
|
|
||||||
"parse-entities/@types/unist": ["@types/[email protected]", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
|
"parse-entities/@types/unist": ["@types/[email protected]", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
|
||||||
|
|
||||||
|
"parse5/entities": ["[email protected]", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
||||||
|
|
||||||
"serve-handler/bytes": ["[email protected]", "", {}, "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw=="],
|
"serve-handler/bytes": ["[email protected]", "", {}, "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw=="],
|
||||||
|
|
||||||
"yuku-ast/@yuku-toolchain/types": ["@yuku-toolchain/[email protected]", "", {}, "sha512-AbUd1775RVkOxJkh8hkldIWoU6kRMTCsZFSZq8Ny53q7GkbaVe5UCfleNZ3RWCoz/ZKE8qwfeB7Cj0xqhLWsKA=="],
|
"yuku-ast/@yuku-toolchain/types": ["@yuku-toolchain/[email protected]", "", {}, "sha512-AbUd1775RVkOxJkh8hkldIWoU6kRMTCsZFSZq8Ny53q7GkbaVe5UCfleNZ3RWCoz/ZKE8qwfeB7Cj0xqhLWsKA=="],
|
||||||
|
|||||||
@@ -3,10 +3,34 @@ title: Write Your Own SDK
|
|||||||
description: Learn to write your own Duckity SDK, for when an official one is not yet available.
|
description: Learn to write your own Duckity SDK, for when an official one is not yet available.
|
||||||
---
|
---
|
||||||
|
|
||||||
|
{/* Code blocks with no language have a bug that cause blank lines to have height 0. To work */}
|
||||||
|
{/* around this issue, we use blank braille characters (⠀) to force the line to display. */}
|
||||||
|
|
||||||
This guide will guide you through writing your own SDK for Duckity in any language. We'll use
|
This guide will guide you through writing your own SDK for Duckity in any language. We'll use
|
||||||
examples for JavaScript and Python, taken from the official SDKs, so you can use as reference to
|
examples for JavaScript and Python, taken from the official SDKs, so you can use as reference to
|
||||||
make one in any language of your choice.
|
make one in any language of your choice.
|
||||||
|
|
||||||
|
This guide intentionally explains everything in simple words and with multiple examples. You'll find
|
||||||
|
it easy to follow this guide as long as you're comfortable in the language you're using.
|
||||||
|
|
||||||
|
The flow is simple:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant Duckling@{ "type": "boundary" } as Duckling
|
||||||
|
participant Server@{ "type": "boundary" } as Server
|
||||||
|
participant Client
|
||||||
|
|
||||||
|
Client ->> Duckling: Can I have a challenge?
|
||||||
|
Duckling ->> Client: Sure, have a challenge string
|
||||||
|
Client --> Client: Solve the challenge received
|
||||||
|
Client ->> Server: Here, I solved the challenge
|
||||||
|
Server ->> Duckling: Is this solution token valid?
|
||||||
|
Duckling ->> Server: Yes/no
|
||||||
|
Server --> Server: Process the rest of the request if ready, otherwise not.
|
||||||
|
Server ->> Client: Response
|
||||||
|
```
|
||||||
|
|
||||||
## Getting a Challenge [step]
|
## Getting a Challenge [step]
|
||||||
|
|
||||||
Duckity serves challenges through the duckling API. It's separate from the management API running on
|
Duckity serves challenges through the duckling API. It's separate from the management API running on
|
||||||
@@ -99,8 +123,8 @@ type Challenge = {
|
|||||||
|
|
||||||
These code pieces implement the decoding as a reference.
|
These code pieces implement the decoding as a reference.
|
||||||
|
|
||||||
<Tabs items={["TypeScript", "Python"]}>
|
<Tabs items={["TypeScript", "Python"]} groupId="reference-language" persist>
|
||||||
<Tab value="TypeScript">
|
<Tab value="TypeScript" id="reference-language-ts">
|
||||||
```ts lineNumbers
|
```ts lineNumbers
|
||||||
interface Challenge {
|
interface Challenge {
|
||||||
// The challenge's unique ID.
|
// The challenge's unique ID.
|
||||||
@@ -126,20 +150,19 @@ These code pieces implement the decoding as a reference.
|
|||||||
* @returns The decoded challenge metadata.
|
* @returns The decoded challenge metadata.
|
||||||
*/
|
*/
|
||||||
export function decode(challenge: string): Challenge {
|
export function decode(challenge: string): Challenge {
|
||||||
// Counts all dots in the challenge string
|
if (challenge.match(/\./g) || []).length != 1) {
|
||||||
if (![1, 2].includes((challenge.match(/\./g) || []).length)) {
|
|
||||||
throw Error("The challenge string contained too many or not enough sections.");
|
throw Error("The challenge string contained too many or not enough sections.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let [base64, _signature] = challenge.split(".", 2);
|
let [base64, _signature] = challenge.split(".", 2);
|
||||||
let json = atob(base64 as string);
|
let json = atob(base64.replaceAll("-", "+").replaceAll("_", "/") as string);
|
||||||
let data: Challenge = JSON.parse(json as string);
|
let data: Challenge = JSON.parse(json as string);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab value="Python">
|
<Tab value="Python" id="reference-language-py">
|
||||||
```py lineNumbers
|
```py lineNumbers
|
||||||
import base64, json
|
import base64, json
|
||||||
|
|
||||||
@@ -177,7 +200,7 @@ These code pieces implement the decoding as a reference.
|
|||||||
ValueError: The challenge string was not valid.
|
ValueError: The challenge string was not valid.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if challenge.count(".") not in [2, 3]:
|
if challenge.count(".") != 1:
|
||||||
raise ValueError("The challenge string contained too many or not enough parts.")
|
raise ValueError("The challenge string contained too many or not enough parts.")
|
||||||
|
|
||||||
raw = challenge.split(".", 1)
|
raw = challenge.split(".", 1)
|
||||||
@@ -221,20 +244,20 @@ The algoritm is simple. Start with `result = 0`, then for every digit in the arr
|
|||||||
`result = (result << 32) | digit`. The following pseudocode displays it simply:
|
`result = (result << 32) | digit`. The following pseudocode displays it simply:
|
||||||
|
|
||||||
```
|
```
|
||||||
function getIntegerFromDigits(digits: number[]):
|
function get_integer_from_digits(digits: number[]) -> integer:
|
||||||
result = 0
|
result = 0
|
||||||
|
⠀
|
||||||
for digit in digits:
|
for digit in digits:
|
||||||
result = (result << 32) | digit
|
result = (result << 32) | digit
|
||||||
|
⠀
|
||||||
return result
|
return result
|
||||||
```
|
```
|
||||||
|
|
||||||
Easy. Then we can use that function on both X and N. The following examples show actual code
|
Easy. Then we can use that function on both X and N. The following examples show actual code
|
||||||
implementations of such function:
|
implementations of such function:
|
||||||
|
|
||||||
<Tabs items={["TypeScript", "Python"]}>
|
<Tabs items={["TypeScript", "Python"]} groupId="reference-language" persist>
|
||||||
<Tab value="TypeScript">
|
<Tab value="TypeScript" id="reference-language-ts">
|
||||||
```ts lineNumbers
|
```ts lineNumbers
|
||||||
/**
|
/**
|
||||||
* Converts an array of u32 digits to a bigint.
|
* Converts an array of u32 digits to a bigint.
|
||||||
@@ -253,7 +276,7 @@ implementations of such function:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab value="Python">
|
<Tab value="Python" id="reference-language-py">
|
||||||
```py lineNumbers
|
```py lineNumbers
|
||||||
# Your life will be brighter if you install `gmpy2-stubs` as a dev dependency, `gmpy2` does
|
# Your life will be brighter if you install `gmpy2-stubs` as a dev dependency, `gmpy2` does
|
||||||
# not have typing stubs integrated, therefore no IDE autocompletion nor help.
|
# not have typing stubs integrated, therefore no IDE autocompletion nor help.
|
||||||
@@ -301,10 +324,10 @@ result of the full computation without needing to build `2^T` nor `X^2^T` nor st
|
|||||||
This step is also not parallelizable. Each iteration needs to know the result of the previous one,
|
This step is also not parallelizable. Each iteration needs to know the result of the previous one,
|
||||||
which leaves no advantage to GPUs. In code, this step would be
|
which leaves no advantage to GPUs. In code, this step would be
|
||||||
|
|
||||||
<Tabs items={["TypeScript", "Python"]}>
|
<Tabs items={["TypeScript", "Python"]} groupId="reference-language" persist>
|
||||||
<Tab value="TypeScript">
|
<Tab value="TypeScript" id="reference-language-ts">
|
||||||
```ts lineNumbers
|
```ts lineNumbers
|
||||||
function solve(nDigits: number[], xDigits: number[], tNumber: number): bigint {
|
function solve(nDigits: number[], xDigits: number[], t: number): bigint {
|
||||||
let n = getBigintFromDigits(nDigits);
|
let n = getBigintFromDigits(nDigits);
|
||||||
let x = getBigintFromDigits(xDigits);
|
let x = getBigintFromDigits(xDigits);
|
||||||
let y = x;
|
let y = x;
|
||||||
@@ -317,7 +340,7 @@ which leaves no advantage to GPUs. In code, this step would be
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab value="Python">
|
<Tab value="Python" id="reference-language-py">
|
||||||
```py lineNumbers
|
```py lineNumbers
|
||||||
import gmpy2
|
import gmpy2
|
||||||
|
|
||||||
@@ -330,7 +353,7 @@ which leaves no advantage to GPUs. In code, this step would be
|
|||||||
for _ in range(t):
|
for _ in range(t):
|
||||||
y = y**2 % n
|
y = y**2 % n
|
||||||
|
|
||||||
return n
|
return y
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
@@ -366,30 +389,30 @@ following parameters (in order):
|
|||||||
The result of hashing those parameters together will be 32 bytes, which will be our `Z` parameter.
|
The result of hashing those parameters together will be 32 bytes, which will be our `Z` parameter.
|
||||||
|
|
||||||
For this, we'll need a new function to convert our bigints to digits (bytes). The algorithm is
|
For this, we'll need a new function to convert our bigints to digits (bytes). The algorithm is
|
||||||
simple, the inverse of what we used for our `getBigintFromDigits` or `get_mpz_from_digits` to decode
|
simple, the inverse of what we used for our `get_bigint_from_digits()` to decode `N` and `X` in the
|
||||||
`N` and `X` in the previous step. In pseudocode, it's
|
previous step. In pseudocode, it's
|
||||||
|
|
||||||
```
|
```
|
||||||
function getDigitsFromBigint(number: bigint, arrayWidth: number, digitByteWidth: number):
|
function get_digits_from_bigint(number: bigint, array_width: number, digit_byte_width: number) -> number[]:
|
||||||
digits = [0] * arrayWidth
|
digits = [0] * array_width
|
||||||
|
⠀
|
||||||
mask = 0;
|
mask = 0;
|
||||||
loop digitByteWidth times:
|
loop digit_byte_width times:
|
||||||
mask = mask << 8
|
mask = mask << 8
|
||||||
mask = mask | 0b1111_1111
|
mask = mask | 0b1111_1111
|
||||||
|
⠀
|
||||||
for i in 0..arrayWidth: # [0; arrayWidth), from 0 to arrayWidth - 1 inclusive.
|
for i in 0..array_width: # [0; array_width), from 0 to array_width - 1 inclusive.
|
||||||
digit = number & mask
|
digit = number & mask
|
||||||
digits[arrayWidth - 1 - i] = digit
|
digits[array_width - 1 - i] = digit
|
||||||
number = number >> (digitByteWidth * 8)
|
number = number >> (digit_byte_width * 8)
|
||||||
|
⠀
|
||||||
return digits
|
return digits
|
||||||
```
|
```
|
||||||
|
|
||||||
In TypeScript and Python, that's
|
In TypeScript and Python, that's
|
||||||
|
|
||||||
<Tabs items={["TypeScript", "Python"]}>
|
<Tabs items={["TypeScript", "Python"]} groupId="reference-language" persist>
|
||||||
<Tab value="TypeScript">
|
<Tab value="TypeScript" id="reference-language-ts">
|
||||||
```ts lineNumbers
|
```ts lineNumbers
|
||||||
/**
|
/**
|
||||||
* Converts a bigint into a MSF digit array.
|
* Converts a bigint into a MSF digit array.
|
||||||
@@ -409,7 +432,7 @@ In TypeScript and Python, that's
|
|||||||
mask |= 0b1111_1111n;
|
mask |= 0b1111_1111n;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = digits - 1; i >= 0; i--) {
|
for (let i = arrayWidth - 1; i >= 0; i--) {
|
||||||
let digit = Number(number & mask);
|
let digit = Number(number & mask);
|
||||||
digits[i] = digit;
|
digits[i] = digit;
|
||||||
number = number >> (digitByteWidth * 8);
|
number = number >> (digitByteWidth * 8);
|
||||||
@@ -419,7 +442,7 @@ In TypeScript and Python, that's
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab value="Python">
|
<Tab value="Python" id="reference-language-py">
|
||||||
```py lineNumbers
|
```py lineNumbers
|
||||||
import gmpy2
|
import gmpy2
|
||||||
|
|
||||||
@@ -468,8 +491,8 @@ z = hasher.finish()
|
|||||||
|
|
||||||
Easy. The following examples show how to do such thing in TypeScript and Python.
|
Easy. The following examples show how to do such thing in TypeScript and Python.
|
||||||
|
|
||||||
<Tabs items={["TypeScript", "Python"]}>
|
<Tabs items={["TypeScript", "Python"]} groupId="reference-language" persist>
|
||||||
<Tab value="TypeScript">
|
<Tab value="TypeScript" id="reference-language-ts">
|
||||||
```ts lineNumbers
|
```ts lineNumbers
|
||||||
/**
|
/**
|
||||||
* Hashes the provided parameters to get Z.
|
* Hashes the provided parameters to get Z.
|
||||||
@@ -495,7 +518,7 @@ Easy. The following examples show how to do such thing in TypeScript and Python.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab value="Python">
|
<Tab value="Python" id="reference-language-py">
|
||||||
```py lineNumbers
|
```py lineNumbers
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
@@ -517,10 +540,10 @@ Easy. The following examples show how to do such thing in TypeScript and Python.
|
|||||||
|
|
||||||
hash = hashlib.sha256()
|
hash = hashlib.sha256()
|
||||||
hash.update(b"duckity")
|
hash.update(b"duckity")
|
||||||
hash.update(get_digits_from_mpz(n, 512, 1))
|
hash.update(bytes(get_digits_from_mpz(n, 512, 1)))
|
||||||
hash.update(get_digits_from_mpz(x, 512, 1))
|
hash.update(bytes(get_digits_from_mpz(x, 512, 1)))
|
||||||
hash.update(get_digits_from_mpz(t, 512, 1))
|
hash.update(bytes(get_digits_from_mpz(t, 512, 1)))
|
||||||
hash.update(get_digits_from_mpz(y, 512, 1))
|
hash.update(bytes(get_digits_from_mpz(y, 512, 1)))
|
||||||
z = hash.digest()
|
z = hash.digest()
|
||||||
|
|
||||||
return z
|
return z
|
||||||
@@ -540,24 +563,24 @@ using the Miller-Rabin algorithm.
|
|||||||
library instead, make sure you test on exactly 40 bases.
|
library instead, make sure you test on exactly 40 bases.
|
||||||
</Callout>
|
</Callout>
|
||||||
|
|
||||||
For this, we'll have to update our previous `getBigintFromDigits` or `get_mpz_from_digits` to allow
|
For this, we'll have to update our previous `get_bigint_from_digits()` to allow arbitrary digit
|
||||||
arbitrary digit sizes. That's simple, instead of hardcoding the digit size we'll pass it as an
|
sizes. That's simple, instead of hardcoding the digit size we'll pass it as an argument to the
|
||||||
argument to the function. Our pseudocode will then look like
|
function. Our pseudocode will then look like
|
||||||
|
|
||||||
```
|
```
|
||||||
function getIntegerFromDigits(digits: number[], digit_byte_width: number):
|
function get_integer_from_digits(digits: number[], digit_byte_width: number):
|
||||||
result = 0
|
result = 0
|
||||||
|
⠀
|
||||||
for digit in digits:
|
for digit in digits:
|
||||||
result = (result << (digit_byte_width * 8)) | digit
|
result = (result << (digit_byte_width * 8)) | digit
|
||||||
|
⠀
|
||||||
return result
|
return result
|
||||||
```
|
```
|
||||||
|
|
||||||
In TypeScript and Python, that's
|
In TypeScript and Python, that's
|
||||||
|
|
||||||
<Tabs items={["TypeScript", "Python"]}>
|
<Tabs items={["TypeScript", "Python"]} groupId="reference-language" persist>
|
||||||
<Tab value="TypeScript">
|
<Tab value="TypeScript" id="reference-language-ts">
|
||||||
```ts lineNumbers
|
```ts lineNumbers
|
||||||
/**
|
/**
|
||||||
* Converts an array of digits to a bigint.
|
* Converts an array of digits to a bigint.
|
||||||
@@ -577,7 +600,7 @@ In TypeScript and Python, that's
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab value="Python">
|
<Tab value="Python" id="reference-language-py">
|
||||||
```py lineNumbers
|
```py lineNumbers
|
||||||
import gmpy2
|
import gmpy2
|
||||||
|
|
||||||
@@ -606,5 +629,492 @@ Now let's interpret `Z` as a bigint. Since `Z` is currently the result of hashin
|
|||||||
together, each byte will be a single-byte digit.
|
together, each byte will be a single-byte digit.
|
||||||
|
|
||||||
```
|
```
|
||||||
z = getBigintFromDigits(z, 1)
|
z = get_bigint_from_digits(z, 1)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To get `L`, we need to find the next prime to our `Z`. E.g. if `Z = 5`, `L = 7`. Note how 5 is
|
||||||
|
already a prime, yet we get the next one regardless.
|
||||||
|
|
||||||
|
Getting the next prime is simple. Starting with `Z + 1`, we call 40 Miller-Rabin tests to check
|
||||||
|
whether it's prime. Many libraries out there already implement Miller-Rabin tests so you don't have
|
||||||
|
to implement it yourself. In case you don't want to, or cannot, use such libraries, we'll explain
|
||||||
|
how to implement it manually here.
|
||||||
|
|
||||||
|
<Callout>
|
||||||
|
The implementation described below is good enough for the values handled by the API. It is not
|
||||||
|
meant to be general purpose beyond this SDK's implementation, and edge cases that do not apply
|
||||||
|
to the calculations needed may be ignored. The Duckling server uses GMP's `next_prime()`
|
||||||
|
function instead of a manual implementation.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
Our implementation will be divided into 4 functions: `is_prime(number, amount_of_bases)`,
|
||||||
|
`is_prime_for_base(number, base)`, `get_next_prime(number, amount_of_bases)`, and
|
||||||
|
`mod_pow(base, exponent, modulus)`.
|
||||||
|
|
||||||
|
#### `mod_pow` [step]
|
||||||
|
|
||||||
|
This function will return the result of `(A^B) % C` without computing `A^B`. This way, we do not
|
||||||
|
have to store numbers too big in memory. The algorithm is simple:
|
||||||
|
|
||||||
|
```
|
||||||
|
function mod_pow(base: bigint, exponent: bigint, modulus: bigint) -> bigint:
|
||||||
|
result = 1
|
||||||
|
⠀
|
||||||
|
while exponent != 0:
|
||||||
|
if (exponent & 1) == 1:
|
||||||
|
result = result * base
|
||||||
|
result = result % modulus
|
||||||
|
⠀
|
||||||
|
base = base * base
|
||||||
|
base = base % modulus
|
||||||
|
⠀
|
||||||
|
exponent = exponent / 2
|
||||||
|
⠀
|
||||||
|
return result
|
||||||
|
```
|
||||||
|
|
||||||
|
In TypeScript, this equals to
|
||||||
|
|
||||||
|
<Tabs items={["TypeScript"]} groupId="reference-language" persist>
|
||||||
|
<Tab value="TypeScript" id="reference-language-ts">
|
||||||
|
```ts lineNumbers
|
||||||
|
/**
|
||||||
|
* Computes `(base ^ exponent) % modulus` efficiently, without storing `base ^ exponent` in
|
||||||
|
* memory.
|
||||||
|
*
|
||||||
|
* @param base The base of the modular exponentiation.
|
||||||
|
* @param exponent The exponent of the modular exponentiation.
|
||||||
|
* @param modulus The modulus of the modular exponentiation.
|
||||||
|
*
|
||||||
|
* @returns The result of the modular exponentiation as a bigint.
|
||||||
|
*/
|
||||||
|
function modPow(base: bigint, exponent: bigint, modulus: bigint): bigint {
|
||||||
|
let result = 1n;
|
||||||
|
|
||||||
|
while (exponent !== 0n) {
|
||||||
|
// (something & 1n) is faster than (something % 2n)
|
||||||
|
if ((exponent & 1n) === 1n) {
|
||||||
|
result *= base;
|
||||||
|
result %= modulus;
|
||||||
|
}
|
||||||
|
|
||||||
|
base **= 2n;
|
||||||
|
base %= modulus;
|
||||||
|
|
||||||
|
exponent /= 2n;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
#### `isPrimeForBase` [step]
|
||||||
|
|
||||||
|
This function will test a number using Miller-Rabin for the specified base. By later testing with
|
||||||
|
different bases in `is_prime()`, we increase the likelyhood of a number being prime. The test works
|
||||||
|
by exponentially reducing the chances of a number being a prime until it is extremely unlikely it's
|
||||||
|
not a prime, without checking whether the number is divisible by other numbers one by one. The
|
||||||
|
algorithm is simple:
|
||||||
|
|
||||||
|
```
|
||||||
|
function is_prime_for_base(number, base) -> boolean:
|
||||||
|
number_minus_one = number - 1
|
||||||
|
odd = number_minus_one
|
||||||
|
base_times = 0
|
||||||
|
⠀
|
||||||
|
while odd % 2 == 0:
|
||||||
|
odd = odd / 2
|
||||||
|
base_times = base_times + 1
|
||||||
|
⠀
|
||||||
|
odd_power = mod_pow(base, odd, number)
|
||||||
|
⠀
|
||||||
|
if odd_power == 1 or odd_power == number_minus_one:
|
||||||
|
return true
|
||||||
|
⠀
|
||||||
|
loop base_times - 1 times:
|
||||||
|
odd_power = (odd_power * odd_power) % number
|
||||||
|
⠀
|
||||||
|
if odd_power == number_minus_one:
|
||||||
|
return true
|
||||||
|
⠀
|
||||||
|
return false
|
||||||
|
```
|
||||||
|
|
||||||
|
In TypeScript, this can be implemented as
|
||||||
|
|
||||||
|
<Tabs items={["TypeScript"]} groupId="reference-language" persist>
|
||||||
|
<Tab value="TypeScript" id="reference-language-ts">
|
||||||
|
```ts lineNumbers
|
||||||
|
/**
|
||||||
|
* Runs a single Miller-Rabin test on a number for a specific base.
|
||||||
|
*
|
||||||
|
* This function assumes basic checks have been run on the number, like division by two and
|
||||||
|
* by five.
|
||||||
|
*
|
||||||
|
* @param number The number to check if is prime.
|
||||||
|
* @param base The base to use to check if the number is prime.
|
||||||
|
*
|
||||||
|
* @returns If the primality test yielded positive for the specified base.
|
||||||
|
*/
|
||||||
|
function isPrimeForBase(number: bigint, base: bigint): boolean {
|
||||||
|
let numberMinusOne = number - 1n;
|
||||||
|
|
||||||
|
let odd = numberMinusOne;
|
||||||
|
let baseTimes = 0;
|
||||||
|
|
||||||
|
while (odd % 2n == 0n) {
|
||||||
|
odd /= 2n;
|
||||||
|
baseTimes += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let oddPower = modPow(base, odd, number);
|
||||||
|
|
||||||
|
if (oddPower == 1n || oddPower == numberMinusOne) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0n; i < baseTimes - 1n; i++) {
|
||||||
|
oddPower = modPow(oddPower, 2, number);
|
||||||
|
|
||||||
|
if (oddPower == numberMinusOne) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
#### `is_prime` [step]
|
||||||
|
|
||||||
|
Checking whether a number is likely a prime is simple. We'll discard numbers that pass basic tests
|
||||||
|
like division by 2 and by five, then we'll call `is_prime_for_base()` in loop starting in 2
|
||||||
|
increasing the loop by 1 after every iteration. All numbers between 2 and `number - 1` inclusive are
|
||||||
|
valid tests. In essence,
|
||||||
|
|
||||||
|
```
|
||||||
|
function is_prime(number, amount_of_tests) -> boolean:
|
||||||
|
if number in [2, 3, 5, 7]:
|
||||||
|
return true
|
||||||
|
⠀
|
||||||
|
if number % 2 == 0 or number % 5 == 0:
|
||||||
|
return false
|
||||||
|
⠀
|
||||||
|
base = 2
|
||||||
|
⠀
|
||||||
|
loop amount_of_tests times:
|
||||||
|
if base >= number:
|
||||||
|
break
|
||||||
|
⠀
|
||||||
|
if not is_prime_for_base(number, base): // Any test returning false means the number is certainly not a prime.
|
||||||
|
return false
|
||||||
|
⠀
|
||||||
|
return true
|
||||||
|
```
|
||||||
|
|
||||||
|
In TypeScript, that is
|
||||||
|
|
||||||
|
<Tabs items={["TypeScript"]} groupId="reference-language" persist>
|
||||||
|
<Tab value="TypeScript" id="reference-language-ts">
|
||||||
|
```ts lineNumbers
|
||||||
|
/**
|
||||||
|
* Runs up to `tests` amount of tests to check whether a number is a prime.
|
||||||
|
*
|
||||||
|
* @param number The number to check for primality.
|
||||||
|
* @param tests The maximum amount of tests to run on the number. Setting this value to a
|
||||||
|
* higher number means better precision at the cost of speed.
|
||||||
|
*
|
||||||
|
* @returns `true` if the number is likely a prime, `false` if the number is not a prime.
|
||||||
|
*/
|
||||||
|
function isPrime(number: bigint, tests: number): boolean {
|
||||||
|
if ([2n, 3n, 5n, 7n].includes(number)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// (something & 1n) is faster than (something % 2n)
|
||||||
|
if ((number & 1n) === 0n) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (number % 5n === 0n) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let base = 2n;
|
||||||
|
|
||||||
|
for (let i = 0n; i < BigInt(tests) && base + i < number; i++) {
|
||||||
|
let prime = isPrimeForBase(number, base + i);
|
||||||
|
|
||||||
|
if (!prime) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
#### `get_next_prime` [step]
|
||||||
|
|
||||||
|
Last but not least, let's call our `is_prime` in a loop starting from `number + 1` until we find a
|
||||||
|
prime. We'll set the amount of tests to 40.
|
||||||
|
|
||||||
|
This algorithm is the most simple of all the `L`-getting flow:
|
||||||
|
|
||||||
|
```
|
||||||
|
loop indefinitely:
|
||||||
|
number = number + 1
|
||||||
|
⠀
|
||||||
|
if is_prime(number, 40):
|
||||||
|
return number
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the `number = number + 1` goes _before_ the `is_prime()` call. We don't want the initial
|
||||||
|
`number` parameter to be returned if it's prime.
|
||||||
|
|
||||||
|
In TypeScript, that's
|
||||||
|
|
||||||
|
<Tabs items={["TypeScript"]} groupId="reference-language" persist>
|
||||||
|
<Tab value="TypeScript" id="reference-language-ts">
|
||||||
|
```ts lineNumbers
|
||||||
|
/**
|
||||||
|
* Returns the first prime after a number.
|
||||||
|
*
|
||||||
|
* Numbers are checked for primality using Miller-Rabin tests on 40 bases each number.
|
||||||
|
*
|
||||||
|
* @param number The number to get the following prime for.
|
||||||
|
*
|
||||||
|
* @returns The number's following prime.
|
||||||
|
*/
|
||||||
|
function getNextPrime(number: bigint): bigint {
|
||||||
|
while (true) {
|
||||||
|
number += 1n;
|
||||||
|
|
||||||
|
if (isPrime(number, 40)) {
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
Now that we can get our next prime, it's finally time to get `L`.
|
||||||
|
|
||||||
|
```
|
||||||
|
l = get_next_prime(z)
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! In TypeScript and Python, that's
|
||||||
|
|
||||||
|
<Tabs items={["TypeScript", "Python"]} groupId="reference-language" persist>
|
||||||
|
<Tab value="TypeScript" id="reference-language-ts">
|
||||||
|
```ts lineNumbers
|
||||||
|
let l = getNextPrime(z);
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
<Tab value="Python" id="reference-language-py">
|
||||||
|
```py lineNumbers
|
||||||
|
import gmpy2
|
||||||
|
|
||||||
|
l = gmpy2.next_prime(z)
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
### Proving the Solution (PI) [step]
|
||||||
|
|
||||||
|
Once we have `L`, our last step towards getting everything we need to get a solution token is
|
||||||
|
getting `PI`. We want a parameter `Q` which comes from `2^T = Q * L + R`. Then for `PI`, we use
|
||||||
|
`PI = X^Q % N`. Not to compute `2^T` directly, we'll use a short algorithm to compute `PI`
|
||||||
|
progressively:
|
||||||
|
|
||||||
|
```
|
||||||
|
r = 1
|
||||||
|
pi = 1
|
||||||
|
⠀
|
||||||
|
loop t times:
|
||||||
|
r = r * 2
|
||||||
|
⠀
|
||||||
|
if r >= l:
|
||||||
|
r = r - l
|
||||||
|
pi = (pi * pi * x) % n
|
||||||
|
else:
|
||||||
|
pi = (pi * pi) % n
|
||||||
|
```
|
||||||
|
|
||||||
|
This way, we get our solution `PI` without filling up our memory with `2^T`. In Python and
|
||||||
|
TypeScript, this is
|
||||||
|
|
||||||
|
<Tabs items={["TypeScript", "Python"]} groupId="reference-language" persist>
|
||||||
|
<Tab value="TypeScript" id="reference-language-ts">
|
||||||
|
```ts lineNumbers
|
||||||
|
let r = 1n;
|
||||||
|
let pi = 1n;
|
||||||
|
|
||||||
|
for (let i = 0n; i < t; i++) {
|
||||||
|
r = r * 2n;
|
||||||
|
|
||||||
|
if (r >= l) {
|
||||||
|
r = r - l;
|
||||||
|
pi = (pi * pi * x) % n;
|
||||||
|
} else {
|
||||||
|
pi = (pi * pi) % n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
<Tab value="Python" id="reference-language-py">
|
||||||
|
```py lineNumbers
|
||||||
|
import gmpy2
|
||||||
|
|
||||||
|
r = gmpy2.mpz(1)
|
||||||
|
pi = gmpy2.mpz(1)
|
||||||
|
|
||||||
|
for i in range(t):
|
||||||
|
r = r * 2
|
||||||
|
|
||||||
|
if r >= l:
|
||||||
|
r = r - l
|
||||||
|
pi = (pi * pi * x) % n
|
||||||
|
else:
|
||||||
|
pi = (pi * pi) % n
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
Once that's done, we'll have our final `PI` value.
|
||||||
|
|
||||||
|
## Encoding the Solution [step]
|
||||||
|
|
||||||
|
Once we have our `Y` and `PI` values, it's time to encode them into a solution token. As stated at
|
||||||
|
the beginning of this guide, challenge tokens are encoded as
|
||||||
|
|
||||||
|
```
|
||||||
|
<challenge-data>.<challenge-signature>
|
||||||
|
```
|
||||||
|
|
||||||
|
We'll now encode our solution into a third segment to have a token that looks like follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
<challenge-data>.<challenge-signature>.<solution-data>
|
||||||
|
```
|
||||||
|
|
||||||
|
Encoding the solution data segment is the inverse of decodind the challenge data segment. We'll
|
||||||
|
convert our `PI` and `Y` values to MSF u32 digit arrays of 128 digits using our
|
||||||
|
`get_digits_from_bigint()`, then encode that as JSON and the JSON as URL-safe Base64 with no
|
||||||
|
padding. The encoded JSON will have the following schema:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type Solution = {
|
||||||
|
pi: number[];
|
||||||
|
y: number[];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
In TypeScript and Python, that's done as follows:
|
||||||
|
|
||||||
|
<Tabs items={["TypeScript", "Python"]} groupId="reference-language" persist>
|
||||||
|
<Tab value="TypeScript" id="reference-language-ts">
|
||||||
|
```ts lineNumbers
|
||||||
|
export function encode(original: string, y: bigint, pi: bigint): string {
|
||||||
|
let solution = {
|
||||||
|
y: getDigitsFromBigint(y, 128, 4),
|
||||||
|
pi: getDigitsFromBigint(pi, 128, 4),
|
||||||
|
};
|
||||||
|
|
||||||
|
let solutionJson = JSON.stringify(solution);
|
||||||
|
let solutionBase64 = btoa(solutionJson)
|
||||||
|
.replaceAll("=", "")
|
||||||
|
.replaceAll("+", "-")
|
||||||
|
.replaceAll("/", "_");
|
||||||
|
|
||||||
|
return `${original}.${solutionBase64}`;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
<Tab value="Python" id="reference-language-py">
|
||||||
|
```py lineNumbers
|
||||||
|
import json
|
||||||
|
|
||||||
|
import base64
|
||||||
|
|
||||||
|
import gmpy2
|
||||||
|
|
||||||
|
def encode(original: str, y: gmpy2.mpz, pi: gmpy2.mpz) -> str:
|
||||||
|
y_digits = get_digits_from_mpz(y, 128, 4)
|
||||||
|
pi_digits = get_digits_from_mpz(pi, 128, 4)
|
||||||
|
|
||||||
|
solution = {"y": y_digits, "pi": pi_digits}
|
||||||
|
solution = json.dumps(solution)
|
||||||
|
solution = base64.urlsafe_b64encode(solution.encode("utf-8")).decode()
|
||||||
|
solution = solution.replace("=", "")
|
||||||
|
|
||||||
|
return f"{original}.{solution}"
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
That's it! Once you have your solution token encoded, you're ready to send it to the server for
|
||||||
|
validation.
|
||||||
|
|
||||||
|
## Validating the Solution [step]
|
||||||
|
|
||||||
|
Validating the solution is done server-side. Usually, the solution token is posted to the
|
||||||
|
application's backend server via a REST API and the server validates the challenge.
|
||||||
|
|
||||||
|
Sending the token from the client to the server is application-specific and not covered by SDKs.
|
||||||
|
Once the server has received the token, the first thing the server should do is to send a request to
|
||||||
|
the Duckling API to check whether the token is valid.
|
||||||
|
|
||||||
|
The request looks like follows:
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /d1/{protection_profile_id}/validate HTTP/1.1
|
||||||
|
Host: api.duckity.com
|
||||||
|
Authorization: Bearer <application-id>
|
||||||
|
Accept: application/json
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"solution": "<solution-token>",
|
||||||
|
"ip": "123.123.123.123"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `"solution"` key in the request's body will be the solution submitted by the client. The `"ip"`
|
||||||
|
will be the IP of the client that submitted the solution token.
|
||||||
|
|
||||||
|
<Callout type="warn">
|
||||||
|
In case the server for any reason does not have a way of getting the client's IP, it can use the
|
||||||
|
`"ip"` key in the challenge's encoded data. Note that this is NOT recommended as it will allow
|
||||||
|
clients to submit solution tokens issued for different clients. The server must use the client's
|
||||||
|
IP whenever possible.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
The duckling API's response will look like follows:
|
||||||
|
|
||||||
|
```http
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"is_valid": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! Your SDK has its core implementation done, now it's time to write documentation for it,
|
||||||
|
error handling where due, and anything specific to the platform you have developed the SDK for.
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ Follow these steps depending on your application:
|
|||||||
|
|
||||||
Then import it in your code:
|
Then import it in your code:
|
||||||
|
|
||||||
```ts twoslash
|
```ts
|
||||||
import duckity from "@duckity/js";
|
import duckity from "@duckity/js";
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ Follow these steps depending on your application:
|
|||||||
Once you have the SDK installed, you can request a challenge whenever you need it using
|
Once you have the SDK installed, you can request a challenge whenever you need it using
|
||||||
`duckity.solve()`.
|
`duckity.solve()`.
|
||||||
|
|
||||||
```ts twoslash
|
```ts
|
||||||
const PROTECTION_PROFILE_ID: string = "";
|
const PROTECTION_PROFILE_ID: string = "";
|
||||||
// ---cut---
|
// ---cut---
|
||||||
import duckity from "@duckity/js";
|
import duckity from "@duckity/js";
|
||||||
@@ -100,19 +100,12 @@ settings), issue the challenge as soon as possible. Note, however, that the chal
|
|||||||
To pass threat correlation keys when issuing a challenge, set them in the `options` argument of
|
To pass threat correlation keys when issuing a challenge, set them in the `options` argument of
|
||||||
`duckity.solve()`.
|
`duckity.solve()`.
|
||||||
|
|
||||||
```ts twoslash
|
```ts
|
||||||
const PROTECTION_PROFILE_ID: string = "py83YHkXV6ZpIsJZGVxzS";
|
const PROTECTION_PROFILE_ID: string = "py83YHkXV6ZpIsJZGVxzS";
|
||||||
// ---cut---
|
// ---cut---
|
||||||
import duckity from "@duckity/js";
|
import duckity from "@duckity/js";
|
||||||
|
|
||||||
let solution = await duckity.solve(
|
let solution = await duckity.solve(PROTECTION_PROFILE_ID);
|
||||||
PROTECTION_PROFILE_ID,
|
|
||||||
{
|
|
||||||
keys: {
|
|
||||||
email: "[email protected]",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using On Self-Hosted Ducklings
|
### Using On Self-Hosted Ducklings
|
||||||
@@ -120,7 +113,7 @@ let solution = await duckity.solve(
|
|||||||
Self-hosted ducklings are hosted at a different domain from Duckity-hosted ducklings. To point it
|
Self-hosted ducklings are hosted at a different domain from Duckity-hosted ducklings. To point it
|
||||||
to a custom domain, change the following setting:
|
to a custom domain, change the following setting:
|
||||||
|
|
||||||
```ts twoslash
|
```ts
|
||||||
const PROTECTION_PROFILE_ID: string = "py83YHkXV6ZpIsJZGVxzS";
|
const PROTECTION_PROFILE_ID: string = "py83YHkXV6ZpIsJZGVxzS";
|
||||||
// ---cut---
|
// ---cut---
|
||||||
import duckity from "@duckity/js";
|
import duckity from "@duckity/js";
|
||||||
|
|||||||
+2
-2
@@ -5,11 +5,11 @@ const withMDX = createMDX();
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const config = {
|
const config = {
|
||||||
serverExternalPackages: ["@takumi-rs/core", "typescript", "twoslash"],
|
serverExternalPackages: ["@takumi-rs/core", "typescript", "twoslash"],
|
||||||
output: "export",
|
// output: "export",
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
allowedDevOrigins: ["duckity.com", "duckity.dev"],
|
allowedDevOrigins: ["duckity.com", "duckity.dev"],
|
||||||
basePath: "/docs",
|
basePath: "/docs",
|
||||||
trailingSlash: true,
|
// trailingSlash: true,
|
||||||
images: {
|
images: {
|
||||||
unoptimized: true,
|
unoptimized: true,
|
||||||
},
|
},
|
||||||
|
|||||||
+3
-1
@@ -19,6 +19,7 @@
|
|||||||
"@orama/orama": "^3.1.18",
|
"@orama/orama": "^3.1.18",
|
||||||
"@typescript/ata": "^0.9.8",
|
"@typescript/ata": "^0.9.8",
|
||||||
"@typescript/vfs": "^1.6.4",
|
"@typescript/vfs": "^1.6.4",
|
||||||
|
"beautiful-mermaid": "^1.1.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"cnfast": "^0.0.8",
|
"cnfast": "^0.0.8",
|
||||||
"fumadocs-core": "16.12.1",
|
"fumadocs-core": "16.12.1",
|
||||||
@@ -26,7 +27,8 @@
|
|||||||
"fumadocs-twoslash": "^3.3.0",
|
"fumadocs-twoslash": "^3.3.0",
|
||||||
"fumadocs-ui": "npm:@fumadocs/[email protected]",
|
"fumadocs-ui": "npm:@fumadocs/[email protected]",
|
||||||
"lucide-react": "^1.3.0",
|
"lucide-react": "^1.3.0",
|
||||||
"next": "^16.2.12",
|
"next": "^16.3.4",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.2.8",
|
"react": "^19.2.8",
|
||||||
"react-dom": "^19.2.8",
|
"react-dom": "^19.2.8",
|
||||||
"takumi-js": "^2.4.0",
|
"takumi-js": "^2.4.0",
|
||||||
|
|||||||
+2
-43
@@ -1,11 +1,8 @@
|
|||||||
import ts from "typescript";
|
|
||||||
import { setupTypeAcquisition } from "@typescript/ata";
|
|
||||||
import { defineConfig, defineDocs } from "fumadocs-mdx/config";
|
import { defineConfig, defineDocs } from "fumadocs-mdx/config";
|
||||||
import { metaSchema, pageSchema } from "fumadocs-core/source/schema";
|
import { metaSchema, pageSchema } from "fumadocs-core/source/schema";
|
||||||
import { remarkSteps } from "fumadocs-core/mdx-plugins/remark-steps";
|
import { remarkSteps } from "fumadocs-core/mdx-plugins/remark-steps";
|
||||||
import { transformerTwoslash } from "fumadocs-twoslash";
|
import { remarkMdxMermaid } from 'fumadocs-core/mdx-plugins';
|
||||||
import { rehypeCodeDefaultOptions } from "fumadocs-core/mdx-plugins";
|
import { rehypeCodeDefaultOptions } from "fumadocs-core/mdx-plugins";
|
||||||
import { createFileSystemTypesCache } from "fumadocs-twoslash/cache-fs";
|
|
||||||
|
|
||||||
// You can customize Zod schemas for frontmatter and `meta.json` here
|
// You can customize Zod schemas for frontmatter and `meta.json` here
|
||||||
// see https://fumadocs.dev/docs/mdx/collections
|
// see https://fumadocs.dev/docs/mdx/collections
|
||||||
@@ -22,34 +19,9 @@ export const docs = defineDocs({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Allow importing @duckity/js from twoslash via `import { Duckity } from "@duckity/js"`.
|
|
||||||
//
|
|
||||||
// For this, we need to create a virtual file system that includes the @duckity/js package from node_modules.
|
|
||||||
const sharedFsMap = new Map<string, string>();
|
|
||||||
|
|
||||||
const imports = `
|
|
||||||
import duckity from "@duckity/js";
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ata = setupTypeAcquisition({
|
|
||||||
projectName: "twoslash-cdn",
|
|
||||||
typescript: ts,
|
|
||||||
logger: console,
|
|
||||||
delegate: {
|
|
||||||
receivedFile: (code, path) => {
|
|
||||||
// ATA paths look like "/node_modules/@types/lodash/index.d.ts"
|
|
||||||
// Strip the leading slash to make it a valid path for the virtual file system
|
|
||||||
|
|
||||||
sharedFsMap.set(path, code);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await ata(imports);
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
mdxOptions: {
|
mdxOptions: {
|
||||||
remarkPlugins: [remarkSteps],
|
remarkPlugins: [remarkSteps, remarkMdxMermaid],
|
||||||
rehypeCodeOptions: {
|
rehypeCodeOptions: {
|
||||||
themes: {
|
themes: {
|
||||||
light: "github-light",
|
light: "github-light",
|
||||||
@@ -57,19 +29,6 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
transformers: [
|
transformers: [
|
||||||
...(rehypeCodeDefaultOptions.transformers ?? []),
|
...(rehypeCodeDefaultOptions.transformers ?? []),
|
||||||
transformerTwoslash({
|
|
||||||
typesCache: createFileSystemTypesCache({}),
|
|
||||||
twoslashOptions: {
|
|
||||||
fsMap: sharedFsMap,
|
|
||||||
compilerOptions: {
|
|
||||||
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
||||||
target: ts.ScriptTarget.ESNext,
|
|
||||||
module: ts.ModuleKind.ESNext,
|
|
||||||
esModuleInterop: true,
|
|
||||||
allowJs: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
|
|
||||||
// important: Shiki doesn't support lazy loading languages for codeblocks in Twoslash popups
|
// important: Shiki doesn't support lazy loading languages for codeblocks in Twoslash popups
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export default async function Page(props: PageProps<"/[[...slug]]">) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateStaticParams() {
|
export async function generateStaticParams(any?: any) {
|
||||||
return source.generateParams();
|
return source.generateParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export async function GET(_req: Request, { params }: RouteContext<'/llms.mdx/doc
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateStaticParams() {
|
export function generateStaticParams(any?: any) {
|
||||||
return source.getPages().map((page) => ({
|
return source.getPages().map((page) => ({
|
||||||
slug: getPageMarkdownUrl(page).segments,
|
slug: getPageMarkdownUrl(page).segments,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import defaultMdxComponents from "fumadocs-ui/mdx";
|
import defaultMdxComponents from "fumadocs-ui/mdx";
|
||||||
import * as TabsComponents from 'fumadocs-ui/components/tabs';
|
import * as TabsComponents from 'fumadocs-ui/components/tabs';
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { Mermaid } from '@/components/mermaid';
|
||||||
import type { MDXComponents } from "mdx/types";
|
import type { MDXComponents } from "mdx/types";
|
||||||
import * as Twoslash from 'fumadocs-twoslash/ui';
|
import * as Twoslash from 'fumadocs-twoslash/ui';
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ export function getMDXComponents(components?: MDXComponents) {
|
|||||||
img: (props) => <Image {...(props as any)} />,
|
img: (props) => <Image {...(props as any)} />,
|
||||||
...TabsComponents,
|
...TabsComponents,
|
||||||
...Twoslash,
|
...Twoslash,
|
||||||
|
Mermaid,
|
||||||
...components,
|
...components,
|
||||||
} satisfies MDXComponents;
|
} satisfies MDXComponents;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { CodeBlock, Pre } from 'fumadocs-ui/components/codeblock';
|
||||||
|
import { renderMermaidSVG } from 'beautiful-mermaid';
|
||||||
|
|
||||||
|
export async function Mermaid({ chart }: { chart: string }) {
|
||||||
|
try {
|
||||||
|
const svg = renderMermaidSVG(chart, {
|
||||||
|
bg: 'var(--color-fd-background)',
|
||||||
|
fg: 'var(--color-fd-foreground)',
|
||||||
|
interactive: true,
|
||||||
|
transparent: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return <div dangerouslySetInnerHTML={{ __html: svg }} />;
|
||||||
|
} catch {
|
||||||
|
return (
|
||||||
|
<CodeBlock title="Mermaid">
|
||||||
|
<Pre>{chart}</Pre>
|
||||||
|
</CodeBlock>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user