mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 16:55:37 +02:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5b3043436 | ||
|
|
623a0db97f | ||
|
|
9827100d19 | ||
|
|
88f499ba5e | ||
|
|
0a1a4cd9b6 | ||
|
|
d7cb47fdd6 | ||
|
|
8b07adb0fc | ||
|
|
661d9d2e56 | ||
|
|
6eee40fcbe | ||
|
|
69d728c4a9 | ||
|
|
be85594bdc | ||
|
|
6e82eff9e2 | ||
|
|
e67a8d2b32 | ||
|
|
8f4bb05143 | ||
|
|
84357ac5bf | ||
|
|
e11012e8d9 | ||
|
|
c0194ecef4 | ||
|
|
ae364f69bd | ||
|
|
d7e6da12ac | ||
|
|
6e19b2e9a0 | ||
|
|
dd20c0049d | ||
|
|
14e4176344 |
@@ -72,7 +72,7 @@ export default component$(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const lockPlay = $(async () => {
|
const lockPlay = $(async () => {
|
||||||
if (!canvas.value || !playState.hasStream) return;
|
if (!canvas.value || !playState.hasStream || playState.nestriLock) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await canvas.value.requestPointerLock();
|
await canvas.value.requestPointerLock();
|
||||||
@@ -156,18 +156,22 @@ export default component$(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// eslint-disable-next-line qwik/no-use-visible-task
|
// eslint-disable-next-line qwik/no-use-visible-task
|
||||||
useVisibleTask$(({ track }) => {
|
useVisibleTask$(({ track }) => {
|
||||||
track(() => canvas.value);
|
track(() => canvas.value);
|
||||||
if (!canvas.value) return; // Ensure canvas is available
|
if (!canvas.value) return; // Ensure canvas is available
|
||||||
|
// Get query parameter "peerURL" from the URL
|
||||||
|
let peerURL = new URLSearchParams(window.location.search).get("peerURL");
|
||||||
|
if (!peerURL || peerURL.length <= 0) {
|
||||||
|
peerURL = "/dnsaddr/relay.dathorse.com/p2p/12D3KooWPK4v5wKYNYx9oXWjqLM8Xix6nm13o91j1Feqq98fLBsw";
|
||||||
|
}
|
||||||
|
|
||||||
setupPointerLockListener();
|
setupPointerLockListener();
|
||||||
try {
|
try {
|
||||||
if (!playState.video) {
|
if (!playState.video) {
|
||||||
playState.video = document.createElement("video") as HTMLVideoElement
|
playState.video = document.createElement("video") as HTMLVideoElement;
|
||||||
playState.video.style.visibility = "hidden";
|
playState.video.style.visibility = "hidden";
|
||||||
playState.webrtc = noSerialize(new WebRTCStream("https://relay.dathorse.com", id, async (mediaStream) => {
|
playState.webrtc = noSerialize(new WebRTCStream(peerURL, id, async (mediaStream) => {
|
||||||
if (playState.video && mediaStream && playState.video.srcObject === null) {
|
if (playState.video && mediaStream && playState.video.srcObject === null) {
|
||||||
console.log("Setting mediastream");
|
console.log("Setting mediastream");
|
||||||
playState.video.srcObject = mediaStream;
|
playState.video.srcObject = mediaStream;
|
||||||
|
|||||||
598
bun.lock
598
bun.lock
@@ -9,6 +9,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/workers-types": "4.20240821.1",
|
"@cloudflare/workers-types": "4.20240821.1",
|
||||||
"@pulumi/pulumi": "^3.134.0",
|
"@pulumi/pulumi": "^3.134.0",
|
||||||
|
"@tsconfig/node22": "^22.0.1",
|
||||||
"@types/aws-lambda": "8.10.147",
|
"@types/aws-lambda": "8.10.147",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.4.5",
|
||||||
@@ -97,11 +98,13 @@
|
|||||||
"sanitize-html": "^2.16.0",
|
"sanitize-html": "^2.16.0",
|
||||||
"sharp": "^0.34.1",
|
"sharp": "^0.34.1",
|
||||||
"steam-session": "*",
|
"steam-session": "*",
|
||||||
|
"xml2js": "^0.6.2",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tsconfig/node20": "^20.1.4",
|
"@tsconfig/node20": "^20.1.4",
|
||||||
"@types/pngjs": "^6.0.5",
|
"@types/pngjs": "^6.0.5",
|
||||||
"@types/sanitize-html": "^2.16.0",
|
"@types/sanitize-html": "^2.16.0",
|
||||||
|
"@types/xml2js": "^0.4.14",
|
||||||
"aws-iot-device-sdk-v2": "^1.21.1",
|
"aws-iot-device-sdk-v2": "^1.21.1",
|
||||||
"aws4fetch": "^1.0.20",
|
"aws4fetch": "^1.0.20",
|
||||||
"mqtt": "^5.10.3",
|
"mqtt": "^5.10.3",
|
||||||
@@ -116,6 +119,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actor-core/bun": "^0.8.0",
|
"@actor-core/bun": "^0.8.0",
|
||||||
"@actor-core/file-system": "^0.8.0",
|
"@actor-core/file-system": "^0.8.0",
|
||||||
|
"@aws-sdk/client-lambda": "^3.821.0",
|
||||||
"@aws-sdk/client-s3": "^3.806.0",
|
"@aws-sdk/client-s3": "^3.806.0",
|
||||||
"@aws-sdk/client-sqs": "^3.806.0",
|
"@aws-sdk/client-sqs": "^3.806.0",
|
||||||
"@nestri/core": "workspace:",
|
"@nestri/core": "workspace:",
|
||||||
@@ -217,12 +221,13 @@
|
|||||||
"@nestri/zero": "*",
|
"@nestri/zero": "*",
|
||||||
"@openauthjs/openauth": "*",
|
"@openauthjs/openauth": "*",
|
||||||
"@openauthjs/solid": "0.0.0-20250311201457",
|
"@openauthjs/solid": "0.0.0-20250311201457",
|
||||||
"@rocicorp/zero": "*",
|
"@rocicorp/zero": "0.20.2025051800",
|
||||||
"@solid-primitives/event-listener": "^2.4.0",
|
"@solid-primitives/event-listener": "^2.4.0",
|
||||||
"@solid-primitives/storage": "^4.3.1",
|
"@solid-primitives/storage": "^4.3.1",
|
||||||
"@solidjs/router": "^0.15.3",
|
"@solidjs/router": "^0.15.3",
|
||||||
"body-scroll-lock-upgrade": "^1.1.0",
|
"body-scroll-lock-upgrade": "^1.1.0",
|
||||||
"eventsource": "^3.0.5",
|
"eventsource": "^3.0.5",
|
||||||
|
"fast-average-color": "9.5.0",
|
||||||
"focus-trap": "^7.6.4",
|
"focus-trap": "^7.6.4",
|
||||||
"hono": "^4.7.4",
|
"hono": "^4.7.4",
|
||||||
"modern-normalize": "^3.0.1",
|
"modern-normalize": "^3.0.1",
|
||||||
@@ -248,7 +253,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestri/core": "*",
|
"@nestri/core": "*",
|
||||||
"@rocicorp/zero": "*",
|
"@rocicorp/zero": "0.20.2025051800",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -265,7 +270,6 @@
|
|||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@openauthjs/openauth": "0.4.3",
|
"@openauthjs/openauth": "0.4.3",
|
||||||
"@rocicorp/zero": "0.20.2025050901",
|
|
||||||
"steam-session": "1.9.3",
|
"steam-session": "1.9.3",
|
||||||
},
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
@@ -305,6 +309,8 @@
|
|||||||
|
|
||||||
"@aws-sdk/client-iot-data-plane": ["@aws-sdk/client-iot-data-plane@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/credential-provider-node": "3.782.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-stream": "^4.2.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-U9KpNxCfSzEmzXCVsN81HMWLHVgMeIYpt9Pey0GSStaZhWxVklRbCfq95Rpg3X1zr5IDLSVFXlRFgVE+m4GdMA=="],
|
"@aws-sdk/client-iot-data-plane": ["@aws-sdk/client-iot-data-plane@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/credential-provider-node": "3.782.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-stream": "^4.2.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-U9KpNxCfSzEmzXCVsN81HMWLHVgMeIYpt9Pey0GSStaZhWxVklRbCfq95Rpg3X1zr5IDLSVFXlRFgVE+m4GdMA=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda": ["@aws-sdk/client-lambda@3.821.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.821.0", "@aws-sdk/credential-provider-node": "3.821.0", "@aws-sdk/middleware-host-header": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", "@aws-sdk/middleware-user-agent": "3.821.0", "@aws-sdk/region-config-resolver": "3.821.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.821.0", "@aws-sdk/util-user-agent-browser": "3.821.0", "@aws-sdk/util-user-agent-node": "3.821.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.5.1", "@smithy/eventstream-serde-browser": "^4.0.4", "@smithy/eventstream-serde-config-resolver": "^4.1.2", "@smithy/eventstream-serde-node": "^4.0.4", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/hash-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", "@smithy/middleware-endpoint": "^4.1.9", "@smithy/middleware-retry": "^4.1.10", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", "@smithy/smithy-client": "^4.4.1", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.17", "@smithy/util-defaults-mode-node": "^4.0.17", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.5", "@smithy/util-stream": "^4.2.2", "@smithy/util-utf8": "^4.0.0", "@smithy/util-waiter": "^4.0.5", "tslib": "^2.6.2" } }, "sha512-251WeT6d0L5EmpIMAhi3C9ujDftG9TEuzt78A8cW4EQp6BZrVTZFG+FZi+Yef06AUQRdZWY1rUx4nE6WOl6Hkw=="],
|
||||||
|
|
||||||
"@aws-sdk/client-rds-data": ["@aws-sdk/client-rds-data@3.758.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.758.0", "@aws-sdk/credential-provider-node": "3.758.0", "@aws-sdk/middleware-host-header": "3.734.0", "@aws-sdk/middleware-logger": "3.734.0", "@aws-sdk/middleware-recursion-detection": "3.734.0", "@aws-sdk/middleware-user-agent": "3.758.0", "@aws-sdk/region-config-resolver": "3.734.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-endpoints": "3.743.0", "@aws-sdk/util-user-agent-browser": "3.734.0", "@aws-sdk/util-user-agent-node": "3.758.0", "@smithy/config-resolver": "^4.0.1", "@smithy/core": "^3.1.5", "@smithy/fetch-http-handler": "^5.0.1", "@smithy/hash-node": "^4.0.1", "@smithy/invalid-dependency": "^4.0.1", "@smithy/middleware-content-length": "^4.0.1", "@smithy/middleware-endpoint": "^4.0.6", "@smithy/middleware-retry": "^4.0.7", "@smithy/middleware-serde": "^4.0.2", "@smithy/middleware-stack": "^4.0.1", "@smithy/node-config-provider": "^4.0.1", "@smithy/node-http-handler": "^4.0.3", "@smithy/protocol-http": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/url-parser": "^4.0.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.7", "@smithy/util-defaults-mode-node": "^4.0.7", "@smithy/util-endpoints": "^3.0.1", "@smithy/util-middleware": "^4.0.1", "@smithy/util-retry": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-ZWgYB23xZISun31m1QjIq+OmKuKwhJj7JDS1OjfGtMjdn+uIAlxdr5cR9TZOMqsHkqJDV2YaPQ5+XRXnUEAZSA=="],
|
"@aws-sdk/client-rds-data": ["@aws-sdk/client-rds-data@3.758.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.758.0", "@aws-sdk/credential-provider-node": "3.758.0", "@aws-sdk/middleware-host-header": "3.734.0", "@aws-sdk/middleware-logger": "3.734.0", "@aws-sdk/middleware-recursion-detection": "3.734.0", "@aws-sdk/middleware-user-agent": "3.758.0", "@aws-sdk/region-config-resolver": "3.734.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-endpoints": "3.743.0", "@aws-sdk/util-user-agent-browser": "3.734.0", "@aws-sdk/util-user-agent-node": "3.758.0", "@smithy/config-resolver": "^4.0.1", "@smithy/core": "^3.1.5", "@smithy/fetch-http-handler": "^5.0.1", "@smithy/hash-node": "^4.0.1", "@smithy/invalid-dependency": "^4.0.1", "@smithy/middleware-content-length": "^4.0.1", "@smithy/middleware-endpoint": "^4.0.6", "@smithy/middleware-retry": "^4.0.7", "@smithy/middleware-serde": "^4.0.2", "@smithy/middleware-stack": "^4.0.1", "@smithy/node-config-provider": "^4.0.1", "@smithy/node-http-handler": "^4.0.3", "@smithy/protocol-http": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/url-parser": "^4.0.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.7", "@smithy/util-defaults-mode-node": "^4.0.7", "@smithy/util-endpoints": "^3.0.1", "@smithy/util-middleware": "^4.0.1", "@smithy/util-retry": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-ZWgYB23xZISun31m1QjIq+OmKuKwhJj7JDS1OjfGtMjdn+uIAlxdr5cR9TZOMqsHkqJDV2YaPQ5+XRXnUEAZSA=="],
|
||||||
|
|
||||||
"@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.806.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.806.0", "@aws-sdk/credential-provider-node": "3.806.0", "@aws-sdk/middleware-bucket-endpoint": "3.806.0", "@aws-sdk/middleware-expect-continue": "3.804.0", "@aws-sdk/middleware-flexible-checksums": "3.806.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-location-constraint": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-sdk-s3": "3.806.0", "@aws-sdk/middleware-ssec": "3.804.0", "@aws-sdk/middleware-user-agent": "3.806.0", "@aws-sdk/region-config-resolver": "3.806.0", "@aws-sdk/signature-v4-multi-region": "3.806.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.806.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.806.0", "@aws-sdk/xml-builder": "3.804.0", "@smithy/config-resolver": "^4.1.1", "@smithy/core": "^3.3.1", "@smithy/eventstream-serde-browser": "^4.0.2", "@smithy/eventstream-serde-config-resolver": "^4.1.0", "@smithy/eventstream-serde-node": "^4.0.2", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-blob-browser": "^4.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/hash-stream-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/md5-js": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.3", "@smithy/middleware-retry": "^4.1.4", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.0", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.3", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.11", "@smithy/util-defaults-mode-node": "^4.0.11", "@smithy/util-endpoints": "^3.0.3", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-stream": "^4.2.0", "@smithy/util-utf8": "^4.0.0", "@smithy/util-waiter": "^4.0.3", "tslib": "^2.6.2" } }, "sha512-kQaBBBxEBU/IJ2wKG+LL2BK+uvBwpdvOA9jy1WhW+U2/DIMwMrjVs7M/ZvTlmVOJwhZaONcJbgQqsN4Yirjj4g=="],
|
"@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.806.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.806.0", "@aws-sdk/credential-provider-node": "3.806.0", "@aws-sdk/middleware-bucket-endpoint": "3.806.0", "@aws-sdk/middleware-expect-continue": "3.804.0", "@aws-sdk/middleware-flexible-checksums": "3.806.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-location-constraint": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-sdk-s3": "3.806.0", "@aws-sdk/middleware-ssec": "3.804.0", "@aws-sdk/middleware-user-agent": "3.806.0", "@aws-sdk/region-config-resolver": "3.806.0", "@aws-sdk/signature-v4-multi-region": "3.806.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.806.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.806.0", "@aws-sdk/xml-builder": "3.804.0", "@smithy/config-resolver": "^4.1.1", "@smithy/core": "^3.3.1", "@smithy/eventstream-serde-browser": "^4.0.2", "@smithy/eventstream-serde-config-resolver": "^4.1.0", "@smithy/eventstream-serde-node": "^4.0.2", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-blob-browser": "^4.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/hash-stream-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/md5-js": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.3", "@smithy/middleware-retry": "^4.1.4", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.0", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.3", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.11", "@smithy/util-defaults-mode-node": "^4.0.11", "@smithy/util-endpoints": "^3.0.3", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-stream": "^4.2.0", "@smithy/util-utf8": "^4.0.0", "@smithy/util-waiter": "^4.0.3", "tslib": "^2.6.2" } }, "sha512-kQaBBBxEBU/IJ2wKG+LL2BK+uvBwpdvOA9jy1WhW+U2/DIMwMrjVs7M/ZvTlmVOJwhZaONcJbgQqsN4Yirjj4g=="],
|
||||||
@@ -917,6 +923,8 @@
|
|||||||
|
|
||||||
"@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.200.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IKJBQxh91qJ+3ssRly5hYEJ8NDHu9oY/B1PXVSCWf7zytmYO9RNLB0Ox9XQ/fJ8m6gY6Q6NtBWlmXfaXt5Uc4Q=="],
|
"@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.200.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IKJBQxh91qJ+3ssRly5hYEJ8NDHu9oY/B1PXVSCWf7zytmYO9RNLB0Ox9XQ/fJ8m6gY6Q6NtBWlmXfaXt5Uc4Q=="],
|
||||||
|
|
||||||
|
"@opentelemetry/auto-instrumentations-node": ["@opentelemetry/auto-instrumentations-node@0.58.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/instrumentation-amqplib": "^0.47.0", "@opentelemetry/instrumentation-aws-lambda": "^0.51.1", "@opentelemetry/instrumentation-aws-sdk": "^0.52.0", "@opentelemetry/instrumentation-bunyan": "^0.46.0", "@opentelemetry/instrumentation-cassandra-driver": "^0.46.0", "@opentelemetry/instrumentation-connect": "^0.44.0", "@opentelemetry/instrumentation-cucumber": "^0.15.0", "@opentelemetry/instrumentation-dataloader": "^0.17.0", "@opentelemetry/instrumentation-dns": "^0.44.0", "@opentelemetry/instrumentation-express": "^0.49.0", "@opentelemetry/instrumentation-fastify": "^0.45.0", "@opentelemetry/instrumentation-fs": "^0.20.0", "@opentelemetry/instrumentation-generic-pool": "^0.44.0", "@opentelemetry/instrumentation-graphql": "^0.48.0", "@opentelemetry/instrumentation-grpc": "^0.200.0", "@opentelemetry/instrumentation-hapi": "^0.46.0", "@opentelemetry/instrumentation-http": "^0.200.0", "@opentelemetry/instrumentation-ioredis": "^0.48.0", "@opentelemetry/instrumentation-kafkajs": "^0.9.2", "@opentelemetry/instrumentation-knex": "^0.45.0", "@opentelemetry/instrumentation-koa": "^0.48.0", "@opentelemetry/instrumentation-lru-memoizer": "^0.45.0", "@opentelemetry/instrumentation-memcached": "^0.44.0", "@opentelemetry/instrumentation-mongodb": "^0.53.0", "@opentelemetry/instrumentation-mongoose": "^0.47.1", "@opentelemetry/instrumentation-mysql": "^0.46.0", "@opentelemetry/instrumentation-mysql2": "^0.46.0", "@opentelemetry/instrumentation-nestjs-core": "^0.46.0", "@opentelemetry/instrumentation-net": "^0.44.0", "@opentelemetry/instrumentation-pg": "^0.52.0", "@opentelemetry/instrumentation-pino": "^0.47.0", "@opentelemetry/instrumentation-redis": "^0.47.0", "@opentelemetry/instrumentation-redis-4": "^0.47.0", "@opentelemetry/instrumentation-restify": "^0.46.0", "@opentelemetry/instrumentation-router": "^0.45.0", "@opentelemetry/instrumentation-runtime-node": "^0.14.0", "@opentelemetry/instrumentation-socket.io": "^0.47.0", "@opentelemetry/instrumentation-tedious": "^0.19.0", "@opentelemetry/instrumentation-undici": "^0.11.0", "@opentelemetry/instrumentation-winston": "^0.45.0", "@opentelemetry/resource-detector-alibaba-cloud": "^0.31.0", "@opentelemetry/resource-detector-aws": "^2.0.0", "@opentelemetry/resource-detector-azure": "^0.7.0", "@opentelemetry/resource-detector-container": "^0.7.0", "@opentelemetry/resource-detector-gcp": "^0.34.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-node": "^0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.4.1", "@opentelemetry/core": "^2.0.0" } }, "sha512-hAsNw5XtFTytQ6GrCspIwKKSamXQGfAvRfqOL93VTqaI1WFBhndyXsNrjAzqULvK0JwMJOuZb77ckdrvJrW3vA=="],
|
||||||
|
|
||||||
"@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@1.30.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA=="],
|
"@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@1.30.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA=="],
|
||||||
|
|
||||||
"@opentelemetry/core": ["@opentelemetry/core@1.30.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ=="],
|
"@opentelemetry/core": ["@opentelemetry/core@1.30.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ=="],
|
||||||
@@ -945,18 +953,110 @@
|
|||||||
|
|
||||||
"@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.55.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.55.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "semver": "^7.5.2", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-YDCMlaQRZkziLL3t6TONRgmmGxDx6MyQDXRD0dknkkgUZtOK5+8MWft1OXzmNu6XfBOdT12MKN5rz+jHUkafKQ=="],
|
"@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.55.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.55.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "semver": "^7.5.2", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-YDCMlaQRZkziLL3t6TONRgmmGxDx6MyQDXRD0dknkkgUZtOK5+8MWft1OXzmNu6XfBOdT12MKN5rz+jHUkafKQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-amqplib": ["@opentelemetry/instrumentation-amqplib@0.47.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-bQboBxolOVDcD4l5QAwqKYpJVKQ8BW82+8tiD5uheu0hDuYgdmDziSAByc8yKS7xpkJw4AYocVP7JwSpQ1hgjg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-aws-lambda": ["@opentelemetry/instrumentation-aws-lambda@0.51.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/aws-lambda": "8.10.147" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-DxUihz1ZcJtkCKFMnsr5IpQtU1TFnz/QhTEkcb95yfVvmdWx97ezbcxE4lGFjvQYMT8q2NsZjor8s8W/jrMU2w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-aws-sdk": ["@opentelemetry/instrumentation-aws-sdk@0.52.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/propagation-utils": "^0.31.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-xMnghwQP/vO9hNNufaHW3SgNprifLPqmssAQ/zjRopbxa6wpBqunWfKYRRoyu89Xlw0X8/hGNoPEh+CIocCryg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-bunyan": ["@opentelemetry/instrumentation-bunyan@0.46.0", "", { "dependencies": { "@opentelemetry/api-logs": "^0.200.0", "@opentelemetry/instrumentation": "^0.200.0", "@types/bunyan": "1.8.11" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-7ERXBAMIVi1rtFG5odsLTLVy6IJZnLLB74fFlPstV7/ZZG04UZ8YFOYVS14jXArcPohY8HFYLbm56dIFCXYI9w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-cassandra-driver": ["@opentelemetry/instrumentation-cassandra-driver@0.46.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-ItT2C32afignjHQosleI/iBjzlHhF+F7tJIK9ty47/CceVNlA9oK39ss9f7o9jmnKvQfhNWffvkXdjc0afwnSQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-connect": ["@opentelemetry/instrumentation-connect@0.44.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/connect": "3.4.38" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-eChFPViU/nkHsCYSp2PCnHnxt/ZmI/N5reHcwmjXbKhEj6TRNJcjLpI+OQksP8lLu0CS9DlDosHEhknCsxLdjQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-cucumber": ["@opentelemetry/instrumentation-cucumber@0.15.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-MOHDzttn5TSBqt4j3/XjBhYNH0iLQP7oX2pumIzXP7dJFTcUtaq6PVakKPtIaqBTTabOKqCJhrF240XGwWefPQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-dataloader": ["@opentelemetry/instrumentation-dataloader@0.17.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-JqovxOo7a65+3A/W+eiqUv7DrDsSvsY0NemHJ4uyVrzD4bpDYofVRdnz/ehYcNerlxVIKU+HcybDmiaoj41DPw=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-dns": ["@opentelemetry/instrumentation-dns@0.44.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+tAFXkFPldOpIba2akqKQ1ukqHET1pZ4pqhrr5x0p+RJ+1a1pPmTt1vCyvSSr634WOY8qMSmzZps++16yxnMbA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-express": ["@opentelemetry/instrumentation-express@0.49.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-j1hbIZzbu7jLQfI/Hz0wHDaniiSWdC3B8/UdH0CEd4lcO8y0pQlz4UTReBaL1BzbkwUhbg6oHuK+m8DXklQPtA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-fastify": ["@opentelemetry/instrumentation-fastify@0.45.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-m94anTFZ6jpvK0G5fXIiq1sB0gCgY2rAL7Cg7svuOh9Roya2RIQz2E5KfCsO1kWCmnHNeTo7wIofoGN7WLPvsA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-fs": ["@opentelemetry/instrumentation-fs@0.20.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-30l45ovjwHb16ImCGVjKCvw5U7X1zKuYY26ii5S+goV8BZ4a/TCpBf2kQxteQjWD05Gl3fzPMZI5aScfPI6Rjw=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-generic-pool": ["@opentelemetry/instrumentation-generic-pool@0.44.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-bY7locZDqmQLEtY2fIJbSnAbHilxfhflaEQHjevFGkaiXc9UMtOvITOy5JKHhYQISpgrvY2WGXKG7YlVyI7uMg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-graphql": ["@opentelemetry/instrumentation-graphql@0.48.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-w1sbf9F9bQTpIWGnKWhH1A+9N9rKxS4eM+AzczgMWp272ZM9lQv4zLTrH5NRST2ltY3nmZ72wkfFrSR0rECi0g=="],
|
||||||
|
|
||||||
"@opentelemetry/instrumentation-grpc": ["@opentelemetry/instrumentation-grpc@0.55.0", "", { "dependencies": { "@opentelemetry/instrumentation": "0.55.0", "@opentelemetry/semantic-conventions": "1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-n2ZH4pRwOy0Vhag/3eKqiyDBwcpUnGgJI9iiIRX7vivE0FMncaLazWphNFezRRaM/LuKwq1TD8pVUvieP68mow=="],
|
"@opentelemetry/instrumentation-grpc": ["@opentelemetry/instrumentation-grpc@0.55.0", "", { "dependencies": { "@opentelemetry/instrumentation": "0.55.0", "@opentelemetry/semantic-conventions": "1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-n2ZH4pRwOy0Vhag/3eKqiyDBwcpUnGgJI9iiIRX7vivE0FMncaLazWphNFezRRaM/LuKwq1TD8pVUvieP68mow=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-hapi": ["@opentelemetry/instrumentation-hapi@0.46.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-573y+ZxywEcq+3+Z3KqcbV45lrVwUKvQiP9OhABVFNX8wHbtM6DPRBmYfqiUkSbIBcOEihm5qH6Gs73Xq0RBEA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-http": ["@opentelemetry/instrumentation-http@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/instrumentation": "0.200.0", "@opentelemetry/semantic-conventions": "^1.29.0", "forwarded-parse": "2.1.2" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-9tqGbCJikhYU68y3k9mi6yWsMyMeCcwoQuHvIXan5VvvPPQ5WIZaV6Mxu/MCVe4swRNoFs8Th+qyj0TZV5ELvw=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-ioredis": ["@opentelemetry/instrumentation-ioredis@0.48.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/redis-common": "^0.37.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-kQhdrn/CAfJIObqbyyGtagWNxPvglJ9FwnWmsfXKodaGskJv/nyvdC9yIcgwzjbkG1pokVUROrvJ0mizqm29Tg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-kafkajs": ["@opentelemetry/instrumentation-kafkajs@0.9.2", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.30.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aRnrLK3gQv6LP64oiXEDdRVwxNe7AvS98SCtNWEGhHy4nv3CdxpN7b7NU53g3PCF7uPQZ1fVW2C6Xc2tt1SIkg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-knex": ["@opentelemetry/instrumentation-knex@0.45.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-2kkyTDUzK/3G3jxTc+NqHSdgi1Mjw2irZ98T/cSyNdlbsnDOMSTHjbm0AxJCV4QYQ4cKW7a8W/BBgxDGlu+mXQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-koa": ["@opentelemetry/instrumentation-koa@0.48.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-LV63v3pxFpjKC0IJO+y5nsGdcH+9Y8Wnn0fhu673XZ5auxqJk2t4nIHuSmls08oRKaX+5q1e+h70XmP/45NJsw=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-lru-memoizer": ["@opentelemetry/instrumentation-lru-memoizer@0.45.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-W2MNx7hPtvSIgEFxFrqdBykdfN0UrShCbJxvMU9fwgqbOdxIrcubPt0i1vmy3Ap6QwSi+HmsRNQD2w3ucbLG3A=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-memcached": ["@opentelemetry/instrumentation-memcached@0.44.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/memcached": "^2.2.6" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-1zABdJlF9Tk0yUv2ELpF6Mk2kw81k+bnB3Sw+D/ssRDcGGCnCNbz+fKJE8dwAPkDP+OcTmiKm6ySREbcyRFzCg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-mongodb": ["@opentelemetry/instrumentation-mongodb@0.53.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-zS2gQJQuG7RZw5yaNG/TnxsOtv1fFkn3ypuDrVLJtJLZtcOr4GYn31jbIA8od+QW/ChZLVcH364iDs+z/xS9wA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-mongoose": ["@opentelemetry/instrumentation-mongoose@0.47.1", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-0OcL5YpZX9PtF55Oi1RtWUdjElJscR9u6NzAdww81EQc3wFfQWmdREUEBeWaDH5jpiomdFp6zDXms622ofEOjg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-mysql": ["@opentelemetry/instrumentation-mysql@0.46.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/mysql": "2.15.26" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Z1NDAv07suIukgL7kxk9cAQX1t/smRMLNOU+q5Aqnhnf/0FIF/N4cX2wg+25IWy0m2PoaPbAVYCKB0aOt5vzAw=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-mysql2": ["@opentelemetry/instrumentation-mysql2@0.46.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@opentelemetry/sql-common": "^0.41.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-JsmIA+aTfHqy2tahjnVWChRipYpYrTy+XFAuUPia9CTaspCx8ZrirPUqYnbnaPEtnzYff2a4LX0B2LT1hKlOiA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-nestjs-core": ["@opentelemetry/instrumentation-nestjs-core@0.46.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.30.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-5cYnBIMZuTSLFUt0pMH+NQNdI5/2YeCVuz29Mo2lkudbBUOvzGmzl/Y6LG1JEw2j6zuJx5IgO5CKNrJqAIzTWA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-net": ["@opentelemetry/instrumentation-net@0.44.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-SmAbOKTi0lgdTN9XMXOaf+4jw670MpiK3pw9/to/kRlTvNWwWA4RD34trCcoL7Gf2IYoXuj56Oo4Z5C7N98ukw=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-pg": ["@opentelemetry/instrumentation-pg@0.52.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@opentelemetry/sql-common": "^0.41.0", "@types/pg": "8.6.1", "@types/pg-pool": "2.0.6" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-OBpqlxTqmFkZGHaHV4Pzd95HkyKVS+vf0N5wVX3BSb8uqsvOrW62I1qt+2jNsZ13dtG5eOzvcsQTMGND76wizA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-pino": ["@opentelemetry/instrumentation-pino@0.47.0", "", { "dependencies": { "@opentelemetry/api-logs": "^0.200.0", "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-OFOy/TGtGXMYWrF4xPKhLN1evdqUpbuoKODzeh3GSjFkcooZZf4m/Hpzu12FV+s0wDBf43oAjXbNJWeCJQMrug=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-redis": ["@opentelemetry/instrumentation-redis@0.47.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/redis-common": "^0.37.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-T2YvuX/LaJEQKgKvIQJlbSMSzxp6oBm+9PMgfn7QcBXzSY9tyeyDF6QjLAKNvxs+BJeQzFmDlahjoEyatzxRWA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-redis-4": ["@opentelemetry/instrumentation-redis-4@0.47.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/redis-common": "^0.37.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-9LywJGp1fmmLj6g1+Rv91pVE3ATle1C/qIya9ZLwPywXTOdFIARI/gvvvlI7uFABoLojj2dSaI/5JQrq4C1HSg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-restify": ["@opentelemetry/instrumentation-restify@0.46.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-du1FjKsTGQH6q8QjG0Bxlg0L79Co/Ey0btKKb2sg7fvg0YX6LKdR2N1fzfne/A9k+WjQ5v28JuUXOk2cEPYU/Q=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-router": ["@opentelemetry/instrumentation-router@0.45.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-CGEeT73Wy/nLQw+obG/mBCIgMbZQKrGG6hzbEdtQ4G2jqI97w7pLWdM4DvkpWVBNcxMpO13dX1nn2OiyZXND3Q=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-runtime-node": ["@opentelemetry/instrumentation-runtime-node@0.14.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-y78dGoFMKwHSz0SD113Gt1dFTcfunpPZXIJh2SzJN27Lyb9FIzuMfjc3Iu3+s/N6qNOLuS9mKnPe3/qVGG4Waw=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-socket.io": ["@opentelemetry/instrumentation-socket.io@0.47.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-qAc+XCcRmZYjs8KJIPv+MMR2wPPPOppwoarzKRR4G+yvOBs1xMwbbkqNHifKga0XcfFX4KVr7Z5QQ6ZZzWyLtg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-tedious": ["@opentelemetry/instrumentation-tedious@0.19.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/tedious": "^4.0.14" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-hNC/Bz+g4RvwaKsbA1VD+9x8X2Ml+fN2uba4dniIdQIrAItLdet4xx/7TEoWYtyVJQozphvpnIsUp52Rw4djCA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-undici": ["@opentelemetry/instrumentation-undici@0.11.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.7.0" } }, "sha512-H6ijJnKVZBB0Lhm6NsaBt0rUz+i52LriLhrpGAE8SazB0jCIVY4MrL2dNib/4w8zA+Fw9zFwERJvKXUIbSD1ew=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-winston": ["@opentelemetry/instrumentation-winston@0.45.0", "", { "dependencies": { "@opentelemetry/api-logs": "^0.200.0", "@opentelemetry/instrumentation": "^0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-LZz3/6QvzoneSqD/xnB8wq/g1fy8oe2PwfZ15zS2YA5mnjuSqlqgl+k3sib7wfIYHMP1D3ajfbDB6UOJBALj/w=="],
|
||||||
|
|
||||||
"@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-transformer": "0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IxJgA3FD7q4V6gGq4bnmQM5nTIyMDkoGFGrBrrDjB6onEiq1pafma55V+bHvGYLWvcqbBbRfezr1GED88lacEQ=="],
|
"@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-transformer": "0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IxJgA3FD7q4V6gGq4bnmQM5nTIyMDkoGFGrBrrDjB6onEiq1pafma55V+bHvGYLWvcqbBbRfezr1GED88lacEQ=="],
|
||||||
|
|
||||||
"@opentelemetry/otlp-grpc-exporter-base": ["@opentelemetry/otlp-grpc-exporter-base@0.200.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-exporter-base": "0.200.0", "@opentelemetry/otlp-transformer": "0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-CK2S+bFgOZ66Bsu5hlDeOX6cvW5FVtVjFFbWuaJP0ELxJKBB6HlbLZQ2phqz/uLj1cWap5xJr/PsR3iGoB7Vqw=="],
|
"@opentelemetry/otlp-grpc-exporter-base": ["@opentelemetry/otlp-grpc-exporter-base@0.200.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-exporter-base": "0.200.0", "@opentelemetry/otlp-transformer": "0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-CK2S+bFgOZ66Bsu5hlDeOX6cvW5FVtVjFFbWuaJP0ELxJKBB6HlbLZQ2phqz/uLj1cWap5xJr/PsR3iGoB7Vqw=="],
|
||||||
|
|
||||||
"@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-logs": "0.200.0", "@opentelemetry/sdk-metrics": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+9YDZbYybOnv7sWzebWOeK6gKyt2XE7iarSyBFkwwnP559pEevKOUD8NyDHhRjCSp13ybh9iVXlMfcj/DwF/yw=="],
|
"@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-logs": "0.200.0", "@opentelemetry/sdk-metrics": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+9YDZbYybOnv7sWzebWOeK6gKyt2XE7iarSyBFkwwnP559pEevKOUD8NyDHhRjCSp13ybh9iVXlMfcj/DwF/yw=="],
|
||||||
|
|
||||||
|
"@opentelemetry/propagation-utils": ["@opentelemetry/propagation-utils@0.31.1", "", { "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-YLNt7SWy4HZwI9d+4+OevQs2Gmof27TkjR3v029UGw8zFOcyONyIQhHHx7doyRbrLpWZtUc91cnCA4mKhArCXw=="],
|
||||||
|
|
||||||
"@opentelemetry/propagator-b3": ["@opentelemetry/propagator-b3@1.30.1", "", { "dependencies": { "@opentelemetry/core": "1.30.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-oATwWWDIJzybAZ4pO76ATN5N6FFbOA1otibAVlS8v90B4S1wClnhRUk7K+2CHAwN1JKYuj4jh/lpCEG5BAqFuQ=="],
|
"@opentelemetry/propagator-b3": ["@opentelemetry/propagator-b3@1.30.1", "", { "dependencies": { "@opentelemetry/core": "1.30.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-oATwWWDIJzybAZ4pO76ATN5N6FFbOA1otibAVlS8v90B4S1wClnhRUk7K+2CHAwN1JKYuj4jh/lpCEG5BAqFuQ=="],
|
||||||
|
|
||||||
"@opentelemetry/propagator-jaeger": ["@opentelemetry/propagator-jaeger@1.30.1", "", { "dependencies": { "@opentelemetry/core": "1.30.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Pj/BfnYEKIOImirH76M4hDaBSx6HyZ2CXUqk+Kj02m6BB80c/yo4BdWkn/1gDFfU+YPY+bPR2U0DKBfdxCKwmg=="],
|
"@opentelemetry/propagator-jaeger": ["@opentelemetry/propagator-jaeger@1.30.1", "", { "dependencies": { "@opentelemetry/core": "1.30.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Pj/BfnYEKIOImirH76M4hDaBSx6HyZ2CXUqk+Kj02m6BB80c/yo4BdWkn/1gDFfU+YPY+bPR2U0DKBfdxCKwmg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/redis-common": ["@opentelemetry/redis-common@0.37.0", "", {}, "sha512-tJwgE6jt32bLs/9J6jhQRKU2EZnsD8qaO13aoFyXwF6s4LhpT7YFHf3Z03MqdILk6BA2BFUhoyh7k9fj9i032A=="],
|
||||||
|
|
||||||
|
"@opentelemetry/resource-detector-alibaba-cloud": ["@opentelemetry/resource-detector-alibaba-cloud@0.31.1", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-RPitvB5oHZsECnK7xtUAFdyBXRdtJbY0eEzQPBrLMQv4l/FN4pETijqv6LcKBbn6tevaoBU2bqOGnVoL4uX4Tg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/resource-detector-aws": ["@opentelemetry/resource-detector-aws@2.1.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-7QG5wQXMiHseKIyU69m8vfZgLhrxFx48DdyaQEYj6GXjE/Xrv1nS3bUwhICjb6+4NorB9+1pFCvJ/4S01CCCjQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/resource-detector-azure": ["@opentelemetry/resource-detector-azure@0.7.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-aR2ALsK+b/+5lLDhK9KTK8rcuKg7+sqa/Cg+QCeasqoy7qby70FRtAbQcZGljJ5BLBcVPYjl1hcTYIUyL3Laww=="],
|
||||||
|
|
||||||
|
"@opentelemetry/resource-detector-container": ["@opentelemetry/resource-detector-container@0.7.1", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-I2vXgdA8mhIlAktIp7NovicalqKPaas9APH5wQxIzMK6jPjZmwS5x0MBW+sTsaFM4pnOf/Md9enoDnnR5CLq5A=="],
|
||||||
|
|
||||||
|
"@opentelemetry/resource-detector-gcp": ["@opentelemetry/resource-detector-gcp@0.34.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.27.0", "gcp-metadata": "^6.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-Mug9Oing1nVQE8pYT33UKuPSEa/wjQTMk3feS9F84h4U7oZIx5Mz3yddj3OHOPgrW/7d1Ve/mG7jmYqBI9tpTg=="],
|
||||||
|
|
||||||
"@opentelemetry/resources": ["@opentelemetry/resources@1.30.1", "", { "dependencies": { "@opentelemetry/core": "1.30.1", "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA=="],
|
"@opentelemetry/resources": ["@opentelemetry/resources@1.30.1", "", { "dependencies": { "@opentelemetry/core": "1.30.1", "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA=="],
|
||||||
|
|
||||||
"@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-VZG870063NLfObmQQNtCVcdXXLzI3vOjjrRENmU37HYiPFa0ZXpXVDsTD02Nh3AT3xYJzQaWKl2X2lQ2l7TWJA=="],
|
"@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-VZG870063NLfObmQQNtCVcdXXLzI3vOjjrRENmU37HYiPFa0ZXpXVDsTD02Nh3AT3xYJzQaWKl2X2lQ2l7TWJA=="],
|
||||||
@@ -971,6 +1071,8 @@
|
|||||||
|
|
||||||
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="],
|
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/sql-common": ["@opentelemetry/sql-common@0.41.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0" } }, "sha512-pmzXctVbEERbqSfiAgdes9Y63xjoOyXcD7B6IXBkVb+vbM7M9U98mn33nGXxPf4dfYR0M+vhcKRZmbSJ7HfqFA=="],
|
||||||
|
|
||||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||||
|
|
||||||
"@oslojs/binary": ["@oslojs/binary@1.0.0", "", {}, "sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ=="],
|
"@oslojs/binary": ["@oslojs/binary@1.0.0", "", {}, "sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ=="],
|
||||||
@@ -1179,7 +1281,7 @@
|
|||||||
|
|
||||||
"@rocicorp/resolver": ["@rocicorp/resolver@1.0.2", "", {}, "sha512-TfjMTQp9cNNqNtHFfa+XHEGdA7NnmDRu+ZJH4YF3dso0Xk/b9DMhg/sl+b6CR4ThFZArXXDsG1j8Mwl34wcOZQ=="],
|
"@rocicorp/resolver": ["@rocicorp/resolver@1.0.2", "", {}, "sha512-TfjMTQp9cNNqNtHFfa+XHEGdA7NnmDRu+ZJH4YF3dso0Xk/b9DMhg/sl+b6CR4ThFZArXXDsG1j8Mwl34wcOZQ=="],
|
||||||
|
|
||||||
"@rocicorp/zero": ["@rocicorp/zero@0.20.2025050901", "", { "dependencies": { "@badrap/valita": "0.3.11", "@databases/escape-identifier": "^1.0.3", "@databases/sql": "^3.3.0", "@dotenvx/dotenvx": "^1.39.0", "@drdgvhbh/postgres-error-codes": "^0.0.6", "@fastify/cors": "^10.0.0", "@fastify/websocket": "^11.0.0", "@google-cloud/precise-date": "^4.0.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/api-logs": "^0.200.0", "@opentelemetry/exporter-logs-otlp-http": "^0.200.0", "@opentelemetry/exporter-metrics-otlp-http": "^0.200.0", "@opentelemetry/exporter-trace-otlp-http": "^0.200.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-logs": "^0.200.0", "@opentelemetry/sdk-metrics": "^2.0.0", "@opentelemetry/sdk-node": "^0.200.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@postgresql-typed/oids": "^0.2.0", "@rocicorp/lock": "^1.0.4", "@rocicorp/logger": "^5.4.0", "@rocicorp/resolver": "^1.0.2", "@rocicorp/zero-sqlite3": "^1.0.4", "@types/basic-auth": "^1.1.8", "basic-auth": "^2.0.1", "chalk": "^5.3.0", "chalk-template": "^1.1.0", "chokidar": "^4.0.1", "command-line-args": "^6.0.1", "command-line-usage": "^7.0.3", "compare-utf8": "^0.1.1", "defu": "^6.1.4", "eventemitter3": "^5.0.1", "fastify": "^5.0.0", "jose": "^5.9.3", "js-xxhash": "^4.0.0", "json-custom-numbers": "^3.1.1", "kasi": "^1.1.0", "nanoid": "^5.1.2", "parse-prometheus-text-format": "^1.1.1", "pg-format": "npm:pg-format-fix@^1.0.5", "postgres": "^3.4.4", "prettier": "^3.5.3", "semver": "^7.5.4", "tsx": "^4.19.1", "typedoc": "^0.28.2", "typedoc-plugin-markdown": "^4.6.1", "url-pattern": "^1.0.3", "ws": "^8.18.1" }, "bin": { "ast-to-zql": "out/zero/src/ast-to-zql.js", "zero-cache": "out/zero/src/cli.js", "analyze-query": "out/zero/src/analyze-query.js", "zero-cache-dev": "out/zero/src/zero-cache-dev.js", "transform-query": "out/zero/src/transform-query.js", "zero-build-schema": "out/zero/src/build-schema.js", "zero-deploy-permissions": "out/zero/src/deploy-permissions.js" } }, "sha512-MDu7KCATF3idDk35bbkome3p0MMpJPtjIeaqi3pLfh67kLTYa6ry37Gvfvk7yyNwXsdJJDgqDraDeQ8EJAweug=="],
|
"@rocicorp/zero": ["@rocicorp/zero@0.20.2025051800", "", { "dependencies": { "@badrap/valita": "0.3.11", "@databases/escape-identifier": "^1.0.3", "@databases/sql": "^3.3.0", "@dotenvx/dotenvx": "^1.39.0", "@drdgvhbh/postgres-error-codes": "^0.0.6", "@fastify/cors": "^10.0.0", "@fastify/websocket": "^11.0.0", "@google-cloud/precise-date": "^4.0.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/api-logs": "^0.200.0", "@opentelemetry/auto-instrumentations-node": "^0.58.1", "@opentelemetry/exporter-logs-otlp-http": "^0.200.0", "@opentelemetry/exporter-metrics-otlp-http": "^0.200.0", "@opentelemetry/exporter-trace-otlp-http": "^0.200.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-logs": "^0.200.0", "@opentelemetry/sdk-metrics": "^2.0.0", "@opentelemetry/sdk-node": "^0.200.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@postgresql-typed/oids": "^0.2.0", "@rocicorp/lock": "^1.0.4", "@rocicorp/logger": "^5.4.0", "@rocicorp/resolver": "^1.0.2", "@rocicorp/zero-sqlite3": "^1.0.4", "@types/basic-auth": "^1.1.8", "basic-auth": "^2.0.1", "chalk": "^5.3.0", "chalk-template": "^1.1.0", "chokidar": "^4.0.1", "command-line-args": "^6.0.1", "command-line-usage": "^7.0.3", "compare-utf8": "^0.1.1", "defu": "^6.1.4", "eventemitter3": "^5.0.1", "fastify": "^5.0.0", "jose": "^5.9.3", "js-xxhash": "^4.0.0", "json-custom-numbers": "^3.1.1", "kasi": "^1.1.0", "nanoid": "^5.1.2", "parse-prometheus-text-format": "^1.1.1", "pg-format": "npm:pg-format-fix@^1.0.5", "postgres": "^3.4.4", "prettier": "^3.5.3", "semver": "^7.5.4", "tsx": "^4.19.1", "typedoc": "^0.28.2", "typedoc-plugin-markdown": "^4.6.1", "url-pattern": "^1.0.3", "ws": "^8.18.1" }, "bin": { "ast-to-zql": "out/zero/src/ast-to-zql.js", "zero-cache": "out/zero/src/cli.js", "analyze-query": "out/zero/src/analyze-query.js", "zero-cache-dev": "out/zero/src/zero-cache-dev.js", "transform-query": "out/zero/src/transform-query.js", "zero-build-schema": "out/zero/src/build-schema.js", "zero-deploy-permissions": "out/zero/src/deploy-permissions.js" } }, "sha512-ttDg0WYciSWnTMVCfnONwfO9fZFcRkZjYLjYWtgq1fIsH10t2XygXQMV7kx1AGFJ+y/0EPvXV3CWXmAhFppk4w=="],
|
||||||
|
|
||||||
"@rocicorp/zero-sqlite3": ["@rocicorp/zero-sqlite3@1.0.4", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" }, "bin": { "zero-sqlite3": "shell.ps1" } }, "sha512-bm+VUdF4CnKVjUj/dSCmVu0hjcyXaF/nKkw2rNhZUjGeBOMRy/hh8z/32h311es4dxCVvcZ3+QHQHMxF2YG5Kw=="],
|
"@rocicorp/zero-sqlite3": ["@rocicorp/zero-sqlite3@1.0.4", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" }, "bin": { "zero-sqlite3": "shell.ps1" } }, "sha512-bm+VUdF4CnKVjUj/dSCmVu0hjcyXaF/nKkw2rNhZUjGeBOMRy/hh8z/32h311es4dxCVvcZ3+QHQHMxF2YG5Kw=="],
|
||||||
|
|
||||||
@@ -1285,15 +1387,15 @@
|
|||||||
|
|
||||||
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.0.2", "", { "dependencies": { "@smithy/node-config-provider": "^4.0.2", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "tslib": "^2.6.2" } }, "sha512-32lVig6jCaWBHnY+OEQ6e6Vnt5vDHaLiydGrwYMW9tPqO688hPGTYRamYJ1EptxEC2rAwJrHWmPoKRBl4iTa8w=="],
|
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.0.2", "", { "dependencies": { "@smithy/node-config-provider": "^4.0.2", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "tslib": "^2.6.2" } }, "sha512-32lVig6jCaWBHnY+OEQ6e6Vnt5vDHaLiydGrwYMW9tPqO688hPGTYRamYJ1EptxEC2rAwJrHWmPoKRBl4iTa8w=="],
|
||||||
|
|
||||||
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.0.2", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.2.0", "@smithy/util-hex-encoding": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-p+f2kLSK7ZrXVfskU/f5dzksKTewZk8pJLPvER3aFHPt76C2MxD9vNatSfLzzQSQB4FNO96RK4PSXfhD1TTeMQ=="],
|
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.0.4", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.3.1", "@smithy/util-hex-encoding": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-7XoWfZqWb/QoR/rAU4VSi0mWnO2vu9/ltS6JZ5ZSZv0eovLVfDfu0/AX4ub33RsJTOth3TiFWSHS5YdztvFnig=="],
|
||||||
|
|
||||||
"@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.0.2", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-CepZCDs2xgVUtH7ZZ7oDdZFH8e6Y2zOv8iiX6RhndH69nlojCALSKK+OXwZUgOtUZEUaZ5e1hULVCHYbCn7pug=="],
|
"@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.0.4", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-3fb/9SYaYqbpy/z/H3yIi0bYKyAa89y6xPmIqwr2vQiUT2St+avRt8UKwsWt9fEdEasc5d/V+QjrviRaX1JRFA=="],
|
||||||
|
|
||||||
"@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.1.0", "", { "dependencies": { "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-1PI+WPZ5TWXrfj3CIoKyUycYynYJgZjuQo8U+sphneOtjsgrttYybdqESFReQrdWJ+LKt6NEdbYzmmfDBmjX2A=="],
|
"@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.1.2", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-JGtambizrWP50xHgbzZI04IWU7LdI0nh/wGbqH3sJesYToMi2j/DcoElqyOcqEIG/D4tNyxgRuaqBXWE3zOFhQ=="],
|
||||||
|
|
||||||
"@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.0.2", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-C5bJ/C6x9ENPMx2cFOirspnF9ZsBVnBMtP6BdPl/qYSuUawdGQ34Lq0dMcf42QTjUZgWGbUIZnz6+zLxJlb9aw=="],
|
"@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.0.4", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-RD6UwNZ5zISpOWPuhVgRz60GkSIp0dy1fuZmj4RYmqLVRtejFqQ16WmfYDdoSoAjlp1LX+FnZo+/hkdmyyGZ1w=="],
|
||||||
|
|
||||||
"@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.0.2", "", { "dependencies": { "@smithy/eventstream-codec": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-St8h9JqzvnbB52FtckiHPN4U/cnXcarMniXRXTKn0r4b4XesZOGiAyUdj1aXbqqn1icSqBlzzUsCl6nPB018ng=="],
|
"@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.0.4", "", { "dependencies": { "@smithy/eventstream-codec": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-UeJpOmLGhq1SLox79QWw/0n2PFX+oPRE1ZyRMxPIaFEfCqWaqpB7BU9C8kpPOGEhLF7AwEqfFbtwNxGy4ReENA=="],
|
||||||
|
|
||||||
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.0.2", "", { "dependencies": { "@smithy/protocol-http": "^5.1.0", "@smithy/querystring-builder": "^4.0.2", "@smithy/types": "^4.2.0", "@smithy/util-base64": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-+9Dz8sakS9pe7f2cBocpJXdeVjMopUDLgZs1yWeu7h++WqSbjUYv/JAJwKwXw1HV6gq1jyWjxuyn24E2GhoEcQ=="],
|
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.0.2", "", { "dependencies": { "@smithy/protocol-http": "^5.1.0", "@smithy/querystring-builder": "^4.0.2", "@smithy/types": "^4.2.0", "@smithy/util-base64": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-+9Dz8sakS9pe7f2cBocpJXdeVjMopUDLgZs1yWeu7h++WqSbjUYv/JAJwKwXw1HV6gq1jyWjxuyn24E2GhoEcQ=="],
|
||||||
|
|
||||||
@@ -1371,7 +1473,7 @@
|
|||||||
|
|
||||||
"@smithy/util-utf8": ["@smithy/util-utf8@4.0.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow=="],
|
"@smithy/util-utf8": ["@smithy/util-utf8@4.0.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow=="],
|
||||||
|
|
||||||
"@smithy/util-waiter": ["@smithy/util-waiter@4.0.3", "", { "dependencies": { "@smithy/abort-controller": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-JtaY3FxmD+te+KSI2FJuEcfNC9T/DGGVf551babM7fAaXhjJUt7oSYurH1Devxd2+BOSUACCgt3buinx4UnmEA=="],
|
"@smithy/util-waiter": ["@smithy/util-waiter@4.0.5", "", { "dependencies": { "@smithy/abort-controller": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-4QvC49HTteI1gfemu0I1syWovJgPvGn7CVUoN9ZFkdvr/cCFkrEL7qNCdx/2eICqDWEGnnr68oMdSIPCLAriSQ=="],
|
||||||
|
|
||||||
"@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="],
|
"@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="],
|
||||||
|
|
||||||
@@ -1427,6 +1529,8 @@
|
|||||||
|
|
||||||
"@tsconfig/node20": ["@tsconfig/node20@20.1.4", "", {}, "sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg=="],
|
"@tsconfig/node20": ["@tsconfig/node20@20.1.4", "", {}, "sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg=="],
|
||||||
|
|
||||||
|
"@tsconfig/node22": ["@tsconfig/node22@22.0.1", "", {}, "sha512-VkgOa3n6jvs1p+r3DiwBqeEwGAwEvnVCg/hIjiANl5IEcqP3G0u5m8cBJspe1t9qjZRlZ7WFgqq5bJrGdgAKMg=="],
|
||||||
|
|
||||||
"@tufjs/canonical-json": ["@tufjs/canonical-json@2.0.0", "", {}, "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA=="],
|
"@tufjs/canonical-json": ["@tufjs/canonical-json@2.0.0", "", {}, "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA=="],
|
||||||
|
|
||||||
"@tufjs/models": ["@tufjs/models@2.0.1", "", { "dependencies": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^9.0.4" } }, "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg=="],
|
"@tufjs/models": ["@tufjs/models@2.0.1", "", { "dependencies": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^9.0.4" } }, "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg=="],
|
||||||
@@ -1449,12 +1553,16 @@
|
|||||||
|
|
||||||
"@types/basic-auth": ["@types/basic-auth@1.1.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-dKcUeixGuZn8pBjcUrf1N7x5K6lWuKuwHHitM2IZ4vwZUDWEhhNtwCWiba8jTA9zn0GQQ+fTFkWpKx8pOU/enw=="],
|
"@types/basic-auth": ["@types/basic-auth@1.1.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-dKcUeixGuZn8pBjcUrf1N7x5K6lWuKuwHHitM2IZ4vwZUDWEhhNtwCWiba8jTA9zn0GQQ+fTFkWpKx8pOU/enw=="],
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.2.13", "", { "dependencies": { "bun-types": "1.2.13" } }, "sha512-u6vXep/i9VBxoJl3GjZsl/BFIsvML8DfVDO0RYLEwtSZSp981kEO1V5NwRcO1CPJ7AmvpbnDCiMKo3JvbDEjAg=="],
|
"@types/bun": ["@types/bun@1.2.15", "", { "dependencies": { "bun-types": "1.2.15" } }, "sha512-U1ljPdBEphF0nw1MIk0hI7kPg7dFdPyM7EenHsp6W5loNHl7zqy6JQf/RKCgnUn2KDzUpkBwHPnEJEjII594bA=="],
|
||||||
|
|
||||||
|
"@types/bunyan": ["@types/bunyan@1.8.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ=="],
|
||||||
|
|
||||||
"@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="],
|
"@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="],
|
||||||
|
|
||||||
"@types/caseless": ["@types/caseless@0.12.5", "", {}, "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg=="],
|
"@types/caseless": ["@types/caseless@0.12.5", "", {}, "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg=="],
|
||||||
|
|
||||||
|
"@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
|
||||||
|
|
||||||
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
|
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
|
||||||
|
|
||||||
"@types/doctrine": ["@types/doctrine@0.0.9", "", {}, "sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA=="],
|
"@types/doctrine": ["@types/doctrine@0.0.9", "", {}, "sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA=="],
|
||||||
@@ -1485,8 +1593,12 @@
|
|||||||
|
|
||||||
"@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="],
|
"@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="],
|
||||||
|
|
||||||
|
"@types/memcached": ["@types/memcached@2.2.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-AM9smvZN55Gzs2wRrqeMHVP7KE8KWgCJO/XL5yCly2xF6EKa4YlbpK+cLSAH4NG/Ah64HrlegmGqW8kYws7Vxg=="],
|
||||||
|
|
||||||
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
|
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
|
||||||
|
|
||||||
|
"@types/mysql": ["@types/mysql@2.15.26", "", { "dependencies": { "@types/node": "*" } }, "sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@20.17.24", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA=="],
|
"@types/node": ["@types/node@20.17.24", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA=="],
|
||||||
|
|
||||||
"@types/node-fetch": ["@types/node-fetch@2.6.12", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.0" } }, "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA=="],
|
"@types/node-fetch": ["@types/node-fetch@2.6.12", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.0" } }, "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA=="],
|
||||||
@@ -1499,6 +1611,10 @@
|
|||||||
|
|
||||||
"@types/parse-path": ["@types/parse-path@7.0.3", "", {}, "sha512-LriObC2+KYZD3FzCrgWGv/qufdUy4eXrxcLgQMfYXgPbLIecKIsVBaQgUPmxSSLcjmYbDTQbMgr6qr6l/eb7Bg=="],
|
"@types/parse-path": ["@types/parse-path@7.0.3", "", {}, "sha512-LriObC2+KYZD3FzCrgWGv/qufdUy4eXrxcLgQMfYXgPbLIecKIsVBaQgUPmxSSLcjmYbDTQbMgr6qr6l/eb7Bg=="],
|
||||||
|
|
||||||
|
"@types/pg": ["@types/pg@8.6.1", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w=="],
|
||||||
|
|
||||||
|
"@types/pg-pool": ["@types/pg-pool@2.0.6", "", { "dependencies": { "@types/pg": "*" } }, "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ=="],
|
||||||
|
|
||||||
"@types/pngjs": ["@types/pngjs@6.0.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-0k5eKfrA83JOZPppLtS2C7OUtyNAl2wKNxfyYl9Q5g9lPkgBl/9hNyAu6HuEH2J4XmIv2znEpkDd0SaZVxW6iQ=="],
|
"@types/pngjs": ["@types/pngjs@6.0.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-0k5eKfrA83JOZPppLtS2C7OUtyNAl2wKNxfyYl9Q5g9lPkgBl/9hNyAu6HuEH2J4XmIv2znEpkDd0SaZVxW6iQ=="],
|
||||||
|
|
||||||
"@types/prop-types": ["@types/prop-types@15.7.14", "", {}, "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ=="],
|
"@types/prop-types": ["@types/prop-types@15.7.14", "", {}, "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ=="],
|
||||||
@@ -1529,6 +1645,8 @@
|
|||||||
|
|
||||||
"@types/steamid": ["@types/steamid@2.0.3", "", {}, "sha512-ozNMQViUYLU+NBN4v7X0bV1O8uTL1bA+WvfHtt9IKcydS4tyYKH7w1vq+xcPGWGL0PRhGtY7C1Zhaeyj2IsETw=="],
|
"@types/steamid": ["@types/steamid@2.0.3", "", {}, "sha512-ozNMQViUYLU+NBN4v7X0bV1O8uTL1bA+WvfHtt9IKcydS4tyYKH7w1vq+xcPGWGL0PRhGtY7C1Zhaeyj2IsETw=="],
|
||||||
|
|
||||||
|
"@types/tedious": ["@types/tedious@4.0.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw=="],
|
||||||
|
|
||||||
"@types/tmp": ["@types/tmp@0.2.6", "", {}, "sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA=="],
|
"@types/tmp": ["@types/tmp@0.2.6", "", {}, "sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA=="],
|
||||||
|
|
||||||
"@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="],
|
"@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="],
|
||||||
@@ -1539,21 +1657,27 @@
|
|||||||
|
|
||||||
"@types/ws": ["@types/ws@8.18.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw=="],
|
"@types/ws": ["@types/ws@8.18.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw=="],
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.32.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/type-utils": "8.32.1", "@typescript-eslint/utils": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg=="],
|
"@types/xml2js": ["@types/xml2js@0.4.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ=="],
|
||||||
|
|
||||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.32.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/types": "8.32.1", "@typescript-eslint/typescript-estree": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg=="],
|
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.33.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.33.1", "@typescript-eslint/type-utils": "8.33.1", "@typescript-eslint/utils": "8.33.1", "@typescript-eslint/visitor-keys": "8.33.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.33.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A=="],
|
||||||
|
|
||||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.32.1", "", { "dependencies": { "@typescript-eslint/types": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1" } }, "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA=="],
|
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.33.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.33.1", "@typescript-eslint/types": "8.33.1", "@typescript-eslint/typescript-estree": "8.33.1", "@typescript-eslint/visitor-keys": "8.33.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA=="],
|
||||||
|
|
||||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.32.1", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.32.1", "@typescript-eslint/utils": "8.32.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA=="],
|
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.33.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.33.1", "@typescript-eslint/types": "^8.33.1", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw=="],
|
||||||
|
|
||||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.32.1", "", {}, "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg=="],
|
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.33.1", "", { "dependencies": { "@typescript-eslint/types": "8.33.1", "@typescript-eslint/visitor-keys": "8.33.1" } }, "sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.32.1", "", { "dependencies": { "@typescript-eslint/types": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg=="],
|
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.33.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g=="],
|
||||||
|
|
||||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.32.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/types": "8.32.1", "@typescript-eslint/typescript-estree": "8.32.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA=="],
|
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.33.1", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.33.1", "@typescript-eslint/utils": "8.33.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww=="],
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.32.1", "", { "dependencies": { "@typescript-eslint/types": "8.32.1", "eslint-visitor-keys": "^4.2.0" } }, "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w=="],
|
"@typescript-eslint/types": ["@typescript-eslint/types@8.33.1", "", {}, "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.33.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.33.1", "@typescript-eslint/tsconfig-utils": "8.33.1", "@typescript-eslint/types": "8.33.1", "@typescript-eslint/visitor-keys": "8.33.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.33.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.33.1", "@typescript-eslint/types": "8.33.1", "@typescript-eslint/typescript-estree": "8.33.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.33.1", "", { "dependencies": { "@typescript-eslint/types": "8.33.1", "eslint-visitor-keys": "^4.2.0" } }, "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ=="],
|
||||||
|
|
||||||
"@typescript/lib-dom": ["@types/web@0.0.115", "", {}, "sha512-IBtUgtxnITC7WTCg4tv6kCnSP0T+fM+3PzQPIzLzJY1DDlhBFKM/9+uMURw14YweWPDiFNIZ94Gc1bJtwow97g=="],
|
"@typescript/lib-dom": ["@types/web@0.0.115", "", {}, "sha512-IBtUgtxnITC7WTCg4tv6kCnSP0T+fM+3PzQPIzLzJY1DDlhBFKM/9+uMURw14YweWPDiFNIZ94Gc1bJtwow97g=="],
|
||||||
|
|
||||||
@@ -1789,6 +1913,8 @@
|
|||||||
|
|
||||||
"bcrypt-pbkdf": ["bcrypt-pbkdf@1.0.2", "", { "dependencies": { "tweetnacl": "^0.14.3" } }, "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w=="],
|
"bcrypt-pbkdf": ["bcrypt-pbkdf@1.0.2", "", { "dependencies": { "tweetnacl": "^0.14.3" } }, "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w=="],
|
||||||
|
|
||||||
|
"bignumber.js": ["bignumber.js@9.3.0", "", {}, "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA=="],
|
||||||
|
|
||||||
"bin-links": ["bin-links@4.0.4", "", { "dependencies": { "cmd-shim": "^6.0.0", "npm-normalize-package-bin": "^3.0.0", "read-cmd-shim": "^4.0.0", "write-file-atomic": "^5.0.0" } }, "sha512-cMtq4W5ZsEwcutJrVId+a/tjt8GSbS+h0oNkdl6+6rBuEv8Ot33Bevj5KPm40t309zuhVic8NjpuL42QCiJWWA=="],
|
"bin-links": ["bin-links@4.0.4", "", { "dependencies": { "cmd-shim": "^6.0.0", "npm-normalize-package-bin": "^3.0.0", "read-cmd-shim": "^4.0.0", "write-file-atomic": "^5.0.0" } }, "sha512-cMtq4W5ZsEwcutJrVId+a/tjt8GSbS+h0oNkdl6+6rBuEv8Ot33Bevj5KPm40t309zuhVic8NjpuL42QCiJWWA=="],
|
||||||
|
|
||||||
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
|
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
|
||||||
@@ -2275,7 +2401,7 @@
|
|||||||
|
|
||||||
"eslint-plugin-jsdoc": ["eslint-plugin-jsdoc@50.6.3", "", { "dependencies": { "@es-joy/jsdoccomment": "~0.49.0", "are-docs-informative": "^0.0.2", "comment-parser": "1.4.1", "debug": "^4.3.6", "escape-string-regexp": "^4.0.0", "espree": "^10.1.0", "esquery": "^1.6.0", "parse-imports": "^2.1.1", "semver": "^7.6.3", "spdx-expression-parse": "^4.0.0", "synckit": "^0.9.1" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, "sha512-NxbJyt1M5zffPcYZ8Nb53/8nnbIScmiLAMdoe0/FAszwb7lcSiX3iYBTsuF7RV84dZZJC8r3NghomrUXsmWvxQ=="],
|
"eslint-plugin-jsdoc": ["eslint-plugin-jsdoc@50.6.3", "", { "dependencies": { "@es-joy/jsdoccomment": "~0.49.0", "are-docs-informative": "^0.0.2", "comment-parser": "1.4.1", "debug": "^4.3.6", "escape-string-regexp": "^4.0.0", "espree": "^10.1.0", "esquery": "^1.6.0", "parse-imports": "^2.1.1", "semver": "^7.6.3", "spdx-expression-parse": "^4.0.0", "synckit": "^0.9.1" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, "sha512-NxbJyt1M5zffPcYZ8Nb53/8nnbIScmiLAMdoe0/FAszwb7lcSiX3iYBTsuF7RV84dZZJC8r3NghomrUXsmWvxQ=="],
|
||||||
|
|
||||||
"eslint-plugin-qwik": ["eslint-plugin-qwik@1.13.0", "", { "dependencies": { "@typescript-eslint/utils": "^8.12.2", "jsx-ast-utils": "^3.3.5" } }, "sha512-7qF4Sq36KY0a8ktCzZac8Q2BBzjAqLb7Stcjwxh4hwO/AD2CcQIl3ZXGvRM57w9siYlmyegUfvxZBCcuvm/4gA=="],
|
"eslint-plugin-qwik": ["eslint-plugin-qwik@1.14.1", "", { "dependencies": { "@typescript-eslint/utils": "^8.31.0", "jsx-ast-utils": "^3.3.5" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" } }, "sha512-N0C8V/6F4EfRdC6EmE7o0VnUod9T16u94C+AD325xA0U4IQoC191uvkGeeJtJ7RXv6UPWz0Evh1aHTfaEILhkg=="],
|
||||||
|
|
||||||
"eslint-plugin-regexp": ["eslint-plugin-regexp@2.7.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.11.0", "comment-parser": "^1.4.0", "jsdoc-type-pratt-parser": "^4.0.0", "refa": "^0.12.1", "regexp-ast-analysis": "^0.7.1", "scslre": "^0.3.0" }, "peerDependencies": { "eslint": ">=8.44.0" } }, "sha512-U8oZI77SBtH8U3ulZ05iu0qEzIizyEDXd+BWHvyVxTOjGwcDcvy/kEpgFG4DYca2ByRLiVPFZ2GeH7j1pdvZTA=="],
|
"eslint-plugin-regexp": ["eslint-plugin-regexp@2.7.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.11.0", "comment-parser": "^1.4.0", "jsdoc-type-pratt-parser": "^4.0.0", "refa": "^0.12.1", "regexp-ast-analysis": "^0.7.1", "scslre": "^0.3.0" }, "peerDependencies": { "eslint": ">=8.44.0" } }, "sha512-U8oZI77SBtH8U3ulZ05iu0qEzIizyEDXd+BWHvyVxTOjGwcDcvy/kEpgFG4DYca2ByRLiVPFZ2GeH7j1pdvZTA=="],
|
||||||
|
|
||||||
@@ -2431,6 +2557,8 @@
|
|||||||
|
|
||||||
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
|
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
|
||||||
|
|
||||||
|
"forwarded-parse": ["forwarded-parse@2.1.2", "", {}, "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw=="],
|
||||||
|
|
||||||
"fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="],
|
"fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="],
|
||||||
|
|
||||||
"framer-motion": ["framer-motion@11.18.2", "", { "dependencies": { "motion-dom": "^11.18.1", "motion-utils": "^11.18.1", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w=="],
|
"framer-motion": ["framer-motion@11.18.2", "", { "dependencies": { "motion-dom": "^11.18.1", "motion-utils": "^11.18.1", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w=="],
|
||||||
@@ -2455,6 +2583,10 @@
|
|||||||
|
|
||||||
"fuse.js": ["fuse.js@6.6.2", "", {}, "sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA=="],
|
"fuse.js": ["fuse.js@6.6.2", "", {}, "sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA=="],
|
||||||
|
|
||||||
|
"gaxios": ["gaxios@6.7.1", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9", "uuid": "^9.0.1" } }, "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ=="],
|
||||||
|
|
||||||
|
"gcp-metadata": ["gcp-metadata@6.1.1", "", { "dependencies": { "gaxios": "^6.1.1", "google-logging-utils": "^0.0.2", "json-bigint": "^1.0.0" } }, "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A=="],
|
||||||
|
|
||||||
"gel": ["gel@2.0.1", "", { "dependencies": { "@petamoriken/float16": "^3.8.7", "debug": "^4.3.4", "env-paths": "^3.0.0", "semver": "^7.6.2", "shell-quote": "^1.8.1", "which": "^4.0.0" }, "bin": { "gel": "dist/cli.mjs" } }, "sha512-gfem3IGvqKqXwEq7XseBogyaRwGsQGuE7Cw/yQsjLGdgiyqX92G1xENPCE0ltunPGcsJIa6XBOTx/PK169mOqw=="],
|
"gel": ["gel@2.0.1", "", { "dependencies": { "@petamoriken/float16": "^3.8.7", "debug": "^4.3.4", "env-paths": "^3.0.0", "semver": "^7.6.2", "shell-quote": "^1.8.1", "which": "^4.0.0" }, "bin": { "gel": "dist/cli.mjs" } }, "sha512-gfem3IGvqKqXwEq7XseBogyaRwGsQGuE7Cw/yQsjLGdgiyqX92G1xENPCE0ltunPGcsJIa6XBOTx/PK169mOqw=="],
|
||||||
|
|
||||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||||
@@ -2507,6 +2639,8 @@
|
|||||||
|
|
||||||
"globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="],
|
"globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="],
|
||||||
|
|
||||||
|
"google-logging-utils": ["google-logging-utils@0.0.2", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="],
|
||||||
|
|
||||||
"google-protobuf": ["google-protobuf@3.21.4", "", {}, "sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ=="],
|
"google-protobuf": ["google-protobuf@3.21.4", "", {}, "sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ=="],
|
||||||
|
|
||||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||||
@@ -2789,6 +2923,8 @@
|
|||||||
|
|
||||||
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||||
|
|
||||||
|
"json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
|
||||||
|
|
||||||
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
||||||
|
|
||||||
"json-custom-numbers": ["json-custom-numbers@3.1.1", "", {}, "sha512-rYIAIuiIRy58aax2tuZb7HawKFATBG848PiguybJh/R+pvC8jxjEOVBQHj4J3U2D4/Y4acBCO4A/glILW8wPoA=="],
|
"json-custom-numbers": ["json-custom-numbers@3.1.1", "", {}, "sha512-rYIAIuiIRy58aax2tuZb7HawKFATBG848PiguybJh/R+pvC8jxjEOVBQHj4J3U2D4/Y4acBCO4A/glILW8wPoA=="],
|
||||||
@@ -4373,6 +4509,72 @@
|
|||||||
|
|
||||||
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/core": ["@aws-sdk/core@3.821.0", "", { "dependencies": { "@aws-sdk/types": "3.821.0", "@smithy/core": "^3.5.1", "@smithy/node-config-provider": "^4.1.3", "@smithy/property-provider": "^4.0.4", "@smithy/protocol-http": "^5.1.2", "@smithy/signature-v4": "^5.1.2", "@smithy/smithy-client": "^4.4.1", "@smithy/types": "^4.3.1", "@smithy/util-middleware": "^4.0.4", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" } }, "sha512-8eB3wKbmfciQFmxFq7hAjy7mXdUs7vBOR5SwT0ZtQBg0Txc18Lc9tMViqqdO6/KU7OukA6ib2IAVSjIJJEN7FQ=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.821.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.821.0", "@aws-sdk/credential-provider-http": "3.821.0", "@aws-sdk/credential-provider-ini": "3.821.0", "@aws-sdk/credential-provider-process": "3.821.0", "@aws-sdk/credential-provider-sso": "3.821.0", "@aws-sdk/credential-provider-web-identity": "3.821.0", "@aws-sdk/types": "3.821.0", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-oBgbcgOXWMgknAfhIdTeHSSVIv+k2LXN9oTbxu1r++o4WWBWrEQ8mHU0Zo9dfr7Uaoqi3pezYZznsBkXnMLEOg=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.821.0", "", { "dependencies": { "@aws-sdk/types": "3.821.0", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-xSMR+sopSeWGx5/4pAGhhfMvGBHioVBbqGvDs6pG64xfNwM5vq5s5v6D04e2i+uSTj4qGa71dLUs5I0UzAK3sw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.821.0", "", { "dependencies": { "@aws-sdk/types": "3.821.0", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-0cvI0ipf2tGx7fXYEEN5fBeZDz2RnHyb9xftSgUsEq7NBxjV0yTZfLJw6Za5rjE6snC80dRN8+bTNR1tuG89zA=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.821.0", "", { "dependencies": { "@aws-sdk/types": "3.821.0", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-efmaifbhBoqKG3bAoEfDdcM8hn1psF+4qa7ykWuYmfmah59JBeqHLfz5W9m9JoTwoKPkFcVLWZxnyZzAnVBOIg=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.821.0", "", { "dependencies": { "@aws-sdk/core": "3.821.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.821.0", "@smithy/core": "^3.5.1", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-rw8q3TxygMg3VrofN04QyWVCCyGwz3bVthYmBZZseENPWG3Krz1OCKcyqjkTcAxMQlEywOske+GIiOasGKnJ3w=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.821.0", "", { "dependencies": { "@aws-sdk/types": "3.821.0", "@smithy/node-config-provider": "^4.1.3", "@smithy/types": "^4.3.1", "@smithy/util-config-provider": "^4.0.0", "@smithy/util-middleware": "^4.0.4", "tslib": "^2.6.2" } }, "sha512-t8og+lRCIIy5nlId0bScNpCkif8sc0LhmtaKsbm0ZPm3sCa/WhCbSZibjbZ28FNjVCV+p0D9RYZx0VDDbtWyjw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/types": ["@aws-sdk/types@3.821.0", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-Znroqdai1a90TlxGaJ+FK1lwC0fHpo97Xjsp5UKGR5JODYm7f9+/fF17ebO1KdoBr/Rm0UIFiF5VmI8ts9F1eA=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.821.0", "", { "dependencies": { "@aws-sdk/types": "3.821.0", "@smithy/types": "^4.3.1", "@smithy/util-endpoints": "^3.0.6", "tslib": "^2.6.2" } }, "sha512-Uknt/zUZnLE76zaAAPEayOeF5/4IZ2puTFXvcSCWHsi9m3tqbb9UozlnlVqvCZLCRWfQryZQoG2W4XSS3qgk5A=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.821.0", "", { "dependencies": { "@aws-sdk/types": "3.821.0", "@smithy/types": "^4.3.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-irWZHyM0Jr1xhC+38OuZ7JB6OXMLPZlj48thElpsO1ZSLRkLZx5+I7VV6k3sp2yZ7BYbKz/G2ojSv4wdm7XTLw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.821.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.821.0", "@aws-sdk/types": "3.821.0", "@smithy/node-config-provider": "^4.1.3", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-YwMXc9EvuzJgnLBTyiQly2juPujXwDgcMHB0iSN92tHe7Dd1jJ1feBmTgdClaaqCeHFUaFpw+3JU/ZUJ6LjR+A=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/config-resolver": ["@smithy/config-resolver@4.1.4", "", { "dependencies": { "@smithy/node-config-provider": "^4.1.3", "@smithy/types": "^4.3.1", "@smithy/util-config-provider": "^4.0.0", "@smithy/util-middleware": "^4.0.4", "tslib": "^2.6.2" } }, "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/core": ["@smithy/core@3.5.1", "", { "dependencies": { "@smithy/middleware-serde": "^4.0.8", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-middleware": "^4.0.4", "@smithy/util-stream": "^4.2.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-xSw7bZEFKwOKrm/iv8e2BLt2ur98YZdrRD6nII8ditQeUsY2Q1JmIQ0rpILOhaLKYxxG2ivnoOpokzr9qLyDWA=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.0.4", "", { "dependencies": { "@smithy/protocol-http": "^5.1.2", "@smithy/querystring-builder": "^4.0.4", "@smithy/types": "^4.3.1", "@smithy/util-base64": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-AMtBR5pHppYMVD7z7G+OlHHAcgAN7v0kVKEpHuTO4Gb199Gowh0taYi9oDStFeUhetkeP55JLSVlTW1n9rFtUw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/hash-node": ["@smithy/hash-node@4.0.4", "", { "dependencies": { "@smithy/types": "^4.3.1", "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.0.4", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.0.4", "", { "dependencies": { "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.1.9", "", { "dependencies": { "@smithy/core": "^3.5.1", "@smithy/middleware-serde": "^4.0.8", "@smithy/node-config-provider": "^4.1.3", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-middleware": "^4.0.4", "tslib": "^2.6.2" } }, "sha512-AjDgX4UjORLltD/LZCBQTwjQqEfyrx/GeDTHcYLzIgf87pIT70tMWnN87NQpJru1K4ITirY2htSOxNECZJCBOg=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/middleware-retry": ["@smithy/middleware-retry@4.1.10", "", { "dependencies": { "@smithy/node-config-provider": "^4.1.3", "@smithy/protocol-http": "^5.1.2", "@smithy/service-error-classification": "^4.0.5", "@smithy/smithy-client": "^4.4.1", "@smithy/types": "^4.3.1", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.5", "tslib": "^2.6.2", "uuid": "^9.0.1" } }, "sha512-RyhcA3sZIIvAo6r48b2Nx2qfg0OnyohlaV0fw415xrQyx5HQ2bvHl9vs/WBiDXIP49mCfws5wX4308c9Pi/isw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/middleware-serde": ["@smithy/middleware-serde@4.0.8", "", { "dependencies": { "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/middleware-stack": ["@smithy/middleware-stack@4.0.4", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/node-config-provider": ["@smithy/node-config-provider@4.1.3", "", { "dependencies": { "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/node-http-handler": ["@smithy/node-http-handler@4.0.6", "", { "dependencies": { "@smithy/abort-controller": "^4.0.4", "@smithy/protocol-http": "^5.1.2", "@smithy/querystring-builder": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/protocol-http": ["@smithy/protocol-http@5.1.2", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/smithy-client": ["@smithy/smithy-client@4.4.1", "", { "dependencies": { "@smithy/core": "^3.5.1", "@smithy/middleware-endpoint": "^4.1.9", "@smithy/middleware-stack": "^4.0.4", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "@smithy/util-stream": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XPbcHRfd0iwx8dY5XCBCGyI7uweMW0oezYezxXcG8ANgvZ5YPuC6Ylh+n0bTHpdU3SCMZOnhzgVklYz+p3fIhw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/types": ["@smithy/types@4.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/url-parser": ["@smithy/url-parser@4.0.4", "", { "dependencies": { "@smithy/querystring-parser": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.0.17", "", { "dependencies": { "@smithy/property-provider": "^4.0.4", "@smithy/smithy-client": "^4.4.1", "@smithy/types": "^4.3.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-HXq5181qnXmIwB7VrwqwP8rsJybHMoYuJnNoXy4PROs2pfSI4sWDMASF2i+7Lo+u64Y6xowhegcdxczowgJtZg=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.0.17", "", { "dependencies": { "@smithy/config-resolver": "^4.1.4", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/node-config-provider": "^4.1.3", "@smithy/property-provider": "^4.0.4", "@smithy/smithy-client": "^4.4.1", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-RfU2A5LjFhEHw4Nwl1GZNitK4AUWu5jGtigAUDoQtfDUvYHpQxcuLw2QGAdKDtKRflIiHSZ8wXBDR36H9R2Ang=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/util-endpoints": ["@smithy/util-endpoints@3.0.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.1.3", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/util-middleware": ["@smithy/util-middleware@4.0.4", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/util-retry": ["@smithy/util-retry@4.0.5", "", { "dependencies": { "@smithy/service-error-classification": "^4.0.5", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-V7MSjVDTlEt/plmOFBn1762Dyu5uqMrV2Pl2X0dYk4XvWfdWJNe9Bs5Bzb56wkCuiWjSfClVMGcsuKrGj7S/yg=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/util-stream": ["@smithy/util-stream@4.2.2", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.0.4", "@smithy/node-http-handler": "^4.0.6", "@smithy/types": "^4.3.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-hex-encoding": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-aI+GLi7MJoVxg24/3J1ipwLoYzgkB4kUfogZfnslcYlynj3xsQ0e7vk4TnTro9hhsS5PvX1mwmkRqqHQjwcU7w=="],
|
||||||
|
|
||||||
"@aws-sdk/client-rds-data/@aws-sdk/core": ["@aws-sdk/core@3.758.0", "", { "dependencies": { "@aws-sdk/types": "3.734.0", "@smithy/core": "^3.1.5", "@smithy/node-config-provider": "^4.0.1", "@smithy/property-provider": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/signature-v4": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/util-middleware": "^4.0.1", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" } }, "sha512-0RswbdR9jt/XKemaLNuxi2gGr4xGlHyGxkTdhSQzCyUe9A9OPCoLl3rIESRguQEech+oJnbHk/wuiwHqTuP9sg=="],
|
"@aws-sdk/client-rds-data/@aws-sdk/core": ["@aws-sdk/core@3.758.0", "", { "dependencies": { "@aws-sdk/types": "3.734.0", "@smithy/core": "^3.1.5", "@smithy/node-config-provider": "^4.0.1", "@smithy/property-provider": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/signature-v4": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/util-middleware": "^4.0.1", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" } }, "sha512-0RswbdR9jt/XKemaLNuxi2gGr4xGlHyGxkTdhSQzCyUe9A9OPCoLl3rIESRguQEech+oJnbHk/wuiwHqTuP9sg=="],
|
||||||
|
|
||||||
"@aws-sdk/client-rds-data/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.758.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.758.0", "@aws-sdk/credential-provider-http": "3.758.0", "@aws-sdk/credential-provider-ini": "3.758.0", "@aws-sdk/credential-provider-process": "3.758.0", "@aws-sdk/credential-provider-sso": "3.758.0", "@aws-sdk/credential-provider-web-identity": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/credential-provider-imds": "^4.0.1", "@smithy/property-provider": "^4.0.1", "@smithy/shared-ini-file-loader": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-+DaMv63wiq7pJrhIQzZYMn4hSarKiizDoJRvyR7WGhnn0oQ/getX9Z0VNCV3i7lIFoLNTb7WMmQ9k7+z/uD5EQ=="],
|
"@aws-sdk/client-rds-data/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.758.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.758.0", "@aws-sdk/credential-provider-http": "3.758.0", "@aws-sdk/credential-provider-ini": "3.758.0", "@aws-sdk/credential-provider-process": "3.758.0", "@aws-sdk/credential-provider-sso": "3.758.0", "@aws-sdk/credential-provider-web-identity": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/credential-provider-imds": "^4.0.1", "@smithy/property-provider": "^4.0.1", "@smithy/shared-ini-file-loader": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-+DaMv63wiq7pJrhIQzZYMn4hSarKiizDoJRvyR7WGhnn0oQ/getX9Z0VNCV3i7lIFoLNTb7WMmQ9k7+z/uD5EQ=="],
|
||||||
@@ -4465,6 +4667,12 @@
|
|||||||
|
|
||||||
"@aws-sdk/client-s3/@smithy/core": ["@smithy/core@3.3.1", "", { "dependencies": { "@smithy/middleware-serde": "^4.0.3", "@smithy/protocol-http": "^5.1.0", "@smithy/types": "^4.2.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-middleware": "^4.0.2", "@smithy/util-stream": "^4.2.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-W7AppgQD3fP1aBmo8wWo0id5zeR2/aYRy067vZsDVaa6v/mdhkg6DxXwEVuSPjZl+ZnvWAQbUMCd5ckw38+tHQ=="],
|
"@aws-sdk/client-s3/@smithy/core": ["@smithy/core@3.3.1", "", { "dependencies": { "@smithy/middleware-serde": "^4.0.3", "@smithy/protocol-http": "^5.1.0", "@smithy/types": "^4.2.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-middleware": "^4.0.2", "@smithy/util-stream": "^4.2.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-W7AppgQD3fP1aBmo8wWo0id5zeR2/aYRy067vZsDVaa6v/mdhkg6DxXwEVuSPjZl+ZnvWAQbUMCd5ckw38+tHQ=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-s3/@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.0.2", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-CepZCDs2xgVUtH7ZZ7oDdZFH8e6Y2zOv8iiX6RhndH69nlojCALSKK+OXwZUgOtUZEUaZ5e1hULVCHYbCn7pug=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-s3/@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.1.0", "", { "dependencies": { "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-1PI+WPZ5TWXrfj3CIoKyUycYynYJgZjuQo8U+sphneOtjsgrttYybdqESFReQrdWJ+LKt6NEdbYzmmfDBmjX2A=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-s3/@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.0.2", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-C5bJ/C6x9ENPMx2cFOirspnF9ZsBVnBMtP6BdPl/qYSuUawdGQ34Lq0dMcf42QTjUZgWGbUIZnz6+zLxJlb9aw=="],
|
||||||
|
|
||||||
"@aws-sdk/client-s3/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.1.4", "", { "dependencies": { "@smithy/core": "^3.3.1", "@smithy/middleware-serde": "^4.0.3", "@smithy/node-config-provider": "^4.1.1", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-middleware": "^4.0.2", "tslib": "^2.6.2" } }, "sha512-qWyYvszzvDjT2AxRvEpNhnMTo8QX9MCAtuSA//kYbXewb+2mEGQCk1UL4dNIrKLcF5KT11dOJtxFYT0kzajq5g=="],
|
"@aws-sdk/client-s3/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.1.4", "", { "dependencies": { "@smithy/core": "^3.3.1", "@smithy/middleware-serde": "^4.0.3", "@smithy/node-config-provider": "^4.1.1", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-middleware": "^4.0.2", "tslib": "^2.6.2" } }, "sha512-qWyYvszzvDjT2AxRvEpNhnMTo8QX9MCAtuSA//kYbXewb+2mEGQCk1UL4dNIrKLcF5KT11dOJtxFYT0kzajq5g=="],
|
||||||
|
|
||||||
"@aws-sdk/client-s3/@smithy/middleware-retry": ["@smithy/middleware-retry@4.1.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.1.1", "@smithy/protocol-http": "^5.1.0", "@smithy/service-error-classification": "^4.0.3", "@smithy/smithy-client": "^4.2.4", "@smithy/types": "^4.2.0", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "tslib": "^2.6.2", "uuid": "^9.0.1" } }, "sha512-eQguCTA2TRGyg4P7gDuhRjL2HtN5OKJXysq3Ufj0EppZe4XBmSyKIvVX9ws9KkD3lkJskw1tfE96wMFsiUShaw=="],
|
"@aws-sdk/client-s3/@smithy/middleware-retry": ["@smithy/middleware-retry@4.1.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.1.1", "@smithy/protocol-http": "^5.1.0", "@smithy/service-error-classification": "^4.0.3", "@smithy/smithy-client": "^4.2.4", "@smithy/types": "^4.2.0", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "tslib": "^2.6.2", "uuid": "^9.0.1" } }, "sha512-eQguCTA2TRGyg4P7gDuhRjL2HtN5OKJXysq3Ufj0EppZe4XBmSyKIvVX9ws9KkD3lkJskw1tfE96wMFsiUShaw=="],
|
||||||
@@ -4481,6 +4689,8 @@
|
|||||||
|
|
||||||
"@aws-sdk/client-s3/@smithy/util-retry": ["@smithy/util-retry@4.0.3", "", { "dependencies": { "@smithy/service-error-classification": "^4.0.3", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DPuYjZQDXmKr/sNvy9Spu8R/ESa2e22wXZzSAY6NkjOLj6spbIje/Aq8rT97iUMdDj0qHMRIe+bTxvlU74d9Ng=="],
|
"@aws-sdk/client-s3/@smithy/util-retry": ["@smithy/util-retry@4.0.3", "", { "dependencies": { "@smithy/service-error-classification": "^4.0.3", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DPuYjZQDXmKr/sNvy9Spu8R/ESa2e22wXZzSAY6NkjOLj6spbIje/Aq8rT97iUMdDj0qHMRIe+bTxvlU74d9Ng=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-s3/@smithy/util-waiter": ["@smithy/util-waiter@4.0.3", "", { "dependencies": { "@smithy/abort-controller": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-JtaY3FxmD+te+KSI2FJuEcfNC9T/DGGVf551babM7fAaXhjJUt7oSYurH1Devxd2+BOSUACCgt3buinx4UnmEA=="],
|
||||||
|
|
||||||
"@aws-sdk/client-sesv2/@aws-sdk/core": ["@aws-sdk/core@3.758.0", "", { "dependencies": { "@aws-sdk/types": "3.734.0", "@smithy/core": "^3.1.5", "@smithy/node-config-provider": "^4.0.1", "@smithy/property-provider": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/signature-v4": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/util-middleware": "^4.0.1", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" } }, "sha512-0RswbdR9jt/XKemaLNuxi2gGr4xGlHyGxkTdhSQzCyUe9A9OPCoLl3rIESRguQEech+oJnbHk/wuiwHqTuP9sg=="],
|
"@aws-sdk/client-sesv2/@aws-sdk/core": ["@aws-sdk/core@3.758.0", "", { "dependencies": { "@aws-sdk/types": "3.734.0", "@smithy/core": "^3.1.5", "@smithy/node-config-provider": "^4.0.1", "@smithy/property-provider": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/signature-v4": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/util-middleware": "^4.0.1", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" } }, "sha512-0RswbdR9jt/XKemaLNuxi2gGr4xGlHyGxkTdhSQzCyUe9A9OPCoLl3rIESRguQEech+oJnbHk/wuiwHqTuP9sg=="],
|
||||||
|
|
||||||
"@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.758.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.758.0", "@aws-sdk/credential-provider-http": "3.758.0", "@aws-sdk/credential-provider-ini": "3.758.0", "@aws-sdk/credential-provider-process": "3.758.0", "@aws-sdk/credential-provider-sso": "3.758.0", "@aws-sdk/credential-provider-web-identity": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/credential-provider-imds": "^4.0.1", "@smithy/property-provider": "^4.0.1", "@smithy/shared-ini-file-loader": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-+DaMv63wiq7pJrhIQzZYMn4hSarKiizDoJRvyR7WGhnn0oQ/getX9Z0VNCV3i7lIFoLNTb7WMmQ9k7+z/uD5EQ=="],
|
"@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.758.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.758.0", "@aws-sdk/credential-provider-http": "3.758.0", "@aws-sdk/credential-provider-ini": "3.758.0", "@aws-sdk/credential-provider-process": "3.758.0", "@aws-sdk/credential-provider-sso": "3.758.0", "@aws-sdk/credential-provider-web-identity": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/credential-provider-imds": "^4.0.1", "@smithy/property-provider": "^4.0.1", "@smithy/shared-ini-file-loader": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-+DaMv63wiq7pJrhIQzZYMn4hSarKiizDoJRvyR7WGhnn0oQ/getX9Z0VNCV3i7lIFoLNTb7WMmQ9k7+z/uD5EQ=="],
|
||||||
@@ -4863,6 +5073,14 @@
|
|||||||
|
|
||||||
"@openauthjs/openevent/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="],
|
"@openauthjs/openevent/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/auto-instrumentations-node/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/auto-instrumentations-node/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/auto-instrumentations-node/@opentelemetry/instrumentation-grpc": ["@opentelemetry/instrumentation-grpc@0.200.0", "", { "dependencies": { "@opentelemetry/instrumentation": "0.200.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-iaPHlO1qb1WlGUq0oTx0rJND/BtBeTAtyEfflu2VwKDe8XZeia7UEOfiSQxnGqVSTwW5F0P1S5UzqeDJotreWQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/auto-instrumentations-node/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
|
||||||
|
|
||||||
"@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
"@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
"@opentelemetry/exporter-logs-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
"@opentelemetry/exporter-logs-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
@@ -4909,8 +5127,170 @@
|
|||||||
|
|
||||||
"@opentelemetry/instrumentation/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.55.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-3cpa+qI45VHYcA5c0bHM6VHo9gicv3p5mlLHNG3rLyjQU8b7e0st1rWtrUn3JbZ3DwwCfhKop4eQ9UuYlC6Pkg=="],
|
"@opentelemetry/instrumentation/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.55.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-3cpa+qI45VHYcA5c0bHM6VHo9gicv3p5mlLHNG3rLyjQU8b7e0st1rWtrUn3JbZ3DwwCfhKop4eQ9UuYlC6Pkg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-amqplib/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-amqplib/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-amqplib/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-aws-lambda/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-aws-lambda/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-aws-sdk/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-aws-sdk/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-aws-sdk/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-bunyan/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-cassandra-driver/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-cassandra-driver/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-connect/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-connect/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-connect/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-cucumber/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-cucumber/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-dataloader/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-dns/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-express/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-express/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-express/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-fastify/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-fastify/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-fastify/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-fs/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-fs/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-generic-pool/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-graphql/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
"@opentelemetry/instrumentation-grpc/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.27.0", "", {}, "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg=="],
|
"@opentelemetry/instrumentation-grpc/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.27.0", "", {}, "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-hapi/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-hapi/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-hapi/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-http/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-http/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-http/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-ioredis/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-ioredis/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-kafkajs/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-kafkajs/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-knex/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-knex/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-koa/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-koa/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-koa/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-lru-memoizer/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-memcached/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-memcached/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-mongodb/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-mongodb/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-mongoose/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-mongoose/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-mongoose/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-mysql/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-mysql/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-mysql2/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-mysql2/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-nestjs-core/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-nestjs-core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-net/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-net/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-pg/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-pg/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-pg/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-pino/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-pino/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-redis/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-redis/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-redis-4/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-redis-4/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-restify/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-restify/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-restify/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-router/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-router/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-runtime-node/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-socket.io/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-socket.io/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-tedious/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-tedious/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-undici/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-undici/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-winston/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
|
||||||
|
|
||||||
"@opentelemetry/otlp-exporter-base/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
"@opentelemetry/otlp-exporter-base/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
"@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
"@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
@@ -4921,6 +5301,36 @@
|
|||||||
|
|
||||||
"@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-qQnYdX+ZCkonM7tA5iU4fSRsVxbFGml8jbxOgipRGMFHKaXKHQ30js03rTobYjKjIfnOsZSbHKWF0/0v0OQGfw=="],
|
"@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-qQnYdX+ZCkonM7tA5iU4fSRsVxbFGml8jbxOgipRGMFHKaXKHQ30js03rTobYjKjIfnOsZSbHKWF0/0v0OQGfw=="],
|
||||||
|
|
||||||
|
"@opentelemetry/resource-detector-alibaba-cloud/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/resource-detector-alibaba-cloud/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/resource-detector-alibaba-cloud/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/resource-detector-aws/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/resource-detector-aws/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/resource-detector-aws/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/resource-detector-azure/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/resource-detector-azure/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/resource-detector-azure/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/resource-detector-container/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/resource-detector-container/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/resource-detector-container/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/resource-detector-gcp/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/resource-detector-gcp/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/resource-detector-gcp/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
"@opentelemetry/sdk-logs/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
"@opentelemetry/sdk-logs/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
"@opentelemetry/sdk-logs/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
|
"@opentelemetry/sdk-logs/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
|
||||||
@@ -4947,6 +5357,8 @@
|
|||||||
|
|
||||||
"@opentelemetry/sdk-node/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
"@opentelemetry/sdk-node/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/sql-common/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
"@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="],
|
"@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="],
|
||||||
|
|
||||||
"@parcel/watcher/detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
|
"@parcel/watcher/detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
|
||||||
@@ -4991,8 +5403,22 @@
|
|||||||
|
|
||||||
"@shikijs/transformers/@shikijs/types": ["@shikijs/types@1.29.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw=="],
|
"@shikijs/transformers/@shikijs/types": ["@shikijs/types@1.29.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw=="],
|
||||||
|
|
||||||
|
"@smithy/eventstream-codec/@smithy/types": ["@smithy/types@4.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA=="],
|
||||||
|
|
||||||
|
"@smithy/eventstream-serde-browser/@smithy/types": ["@smithy/types@4.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA=="],
|
||||||
|
|
||||||
|
"@smithy/eventstream-serde-config-resolver/@smithy/types": ["@smithy/types@4.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA=="],
|
||||||
|
|
||||||
|
"@smithy/eventstream-serde-node/@smithy/types": ["@smithy/types@4.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA=="],
|
||||||
|
|
||||||
|
"@smithy/eventstream-serde-universal/@smithy/types": ["@smithy/types@4.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA=="],
|
||||||
|
|
||||||
"@smithy/middleware-retry/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
"@smithy/middleware-retry/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||||
|
|
||||||
|
"@smithy/util-waiter/@smithy/abort-controller": ["@smithy/abort-controller@4.0.4", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA=="],
|
||||||
|
|
||||||
|
"@smithy/util-waiter/@smithy/types": ["@smithy/types@4.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA=="],
|
||||||
|
|
||||||
"@stylistic/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.26.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.26.0", "@typescript-eslint/types": "8.26.0", "@typescript-eslint/typescript-estree": "8.26.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig=="],
|
"@stylistic/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.26.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.26.0", "@typescript-eslint/types": "8.26.0", "@typescript-eslint/typescript-estree": "8.26.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig=="],
|
||||||
|
|
||||||
"@stylistic/eslint-plugin/eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="],
|
"@stylistic/eslint-plugin/eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="],
|
||||||
@@ -5007,16 +5433,26 @@
|
|||||||
|
|
||||||
"@types/basic-auth/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
"@types/basic-auth/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
||||||
|
|
||||||
"@types/bun/bun-types": ["bun-types@1.2.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-rRjA1T6n7wto4gxhAO/ErZEtOXyEZEmnIHQfl0Dt1QQSB4QV0iP6BZ9/YB5fZaHFQ2dwHFrmPaRQ9GGMX01k9Q=="],
|
"@types/bun/bun-types": ["bun-types@1.2.15", "", { "dependencies": { "@types/node": "*" } }, "sha512-NarRIaS+iOaQU1JPfyKhZm4AsUOrwUOqRNHY0XxI8GI8jYxiLXLcdjYMG9UKS+fwWasc1uw1htV9AX24dD+p4w=="],
|
||||||
|
|
||||||
|
"@types/bunyan/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
||||||
|
|
||||||
"@types/cacheable-request/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
"@types/cacheable-request/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
||||||
|
|
||||||
|
"@types/connect/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
||||||
|
|
||||||
"@types/keyv/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
"@types/keyv/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
||||||
|
|
||||||
|
"@types/memcached/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
||||||
|
|
||||||
|
"@types/mysql/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
||||||
|
|
||||||
"@types/node-fetch/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
"@types/node-fetch/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
||||||
|
|
||||||
"@types/node-fetch/form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="],
|
"@types/node-fetch/form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="],
|
||||||
|
|
||||||
|
"@types/pg/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
||||||
|
|
||||||
"@types/pngjs/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
"@types/pngjs/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
||||||
|
|
||||||
"@types/qrcode/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
"@types/qrcode/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
||||||
@@ -5029,8 +5465,12 @@
|
|||||||
|
|
||||||
"@types/steamcommunity/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
"@types/steamcommunity/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
||||||
|
|
||||||
|
"@types/tedious/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
||||||
|
|
||||||
"@types/ws/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
"@types/ws/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
||||||
|
|
||||||
|
"@types/xml2js/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||||
|
|
||||||
"@typescript-eslint/utils/@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=="],
|
"@typescript-eslint/utils/@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=="],
|
||||||
@@ -5203,7 +5643,7 @@
|
|||||||
|
|
||||||
"eslint-plugin-jsdoc/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=="],
|
"eslint-plugin-jsdoc/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=="],
|
||||||
|
|
||||||
"eslint-plugin-qwik/@typescript-eslint/utils": ["@typescript-eslint/utils@8.28.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.28.0", "@typescript-eslint/types": "8.28.0", "@typescript-eslint/typescript-estree": "8.28.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-OELa9hbTYciYITqgurT1u/SzpQVtDLmQMFzy/N8pQE+tefOyCWT79jHsav294aTqV1q1u+VzqDGbuujvRYaeSQ=="],
|
"eslint-plugin-qwik/@typescript-eslint/utils": ["@typescript-eslint/utils@8.33.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.33.0", "@typescript-eslint/types": "8.33.0", "@typescript-eslint/typescript-estree": "8.33.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-lPFuQaLA9aSNa7D5u2EpRiqdAUhzShwGg/nhpBlc4GR6kcTABttCuyjFs8BcEZ8VWrjCBof/bePhP3Q3fS+Yrw=="],
|
||||||
|
|
||||||
"eslint-plugin-unicorn/globals": ["globals@15.15.0", "", {}, "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg=="],
|
"eslint-plugin-unicorn/globals": ["globals@15.15.0", "", {}, "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg=="],
|
||||||
|
|
||||||
@@ -5233,6 +5673,8 @@
|
|||||||
|
|
||||||
"form-data/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
"form-data/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||||
|
|
||||||
|
"gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||||
|
|
||||||
"gel/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
|
"gel/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
|
||||||
|
|
||||||
"get-source/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
"get-source/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||||
@@ -5691,6 +6133,54 @@
|
|||||||
|
|
||||||
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.0.4", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.1.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.0.0", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "@smithy/util-hex-encoding": "^4.0.0", "@smithy/util-middleware": "^4.0.4", "@smithy/util-uri-escape": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.821.0", "", { "dependencies": { "@aws-sdk/core": "3.821.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-C+s/A72pd7CXwEsJj9+Uq9T726iIfIF18hGRY8o82xcIEfOyakiPnlisku8zZOaAu+jm0CihbbYN4NyYNQ+HZQ=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.821.0", "", { "dependencies": { "@aws-sdk/core": "3.821.0", "@aws-sdk/types": "3.821.0", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/node-http-handler": "^4.0.6", "@smithy/property-provider": "^4.0.4", "@smithy/protocol-http": "^5.1.2", "@smithy/smithy-client": "^4.4.1", "@smithy/types": "^4.3.1", "@smithy/util-stream": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-gIRzTLnAsRfRSNarCag7G7rhcHagz4x5nNTWRihQs5cwTOghEExDy7Tj5m4TEkv3dcTAsNn+l4tnR4nZXo6R+Q=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.821.0", "", { "dependencies": { "@aws-sdk/core": "3.821.0", "@aws-sdk/credential-provider-env": "3.821.0", "@aws-sdk/credential-provider-http": "3.821.0", "@aws-sdk/credential-provider-process": "3.821.0", "@aws-sdk/credential-provider-sso": "3.821.0", "@aws-sdk/credential-provider-web-identity": "3.821.0", "@aws-sdk/nested-clients": "3.821.0", "@aws-sdk/types": "3.821.0", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-VRTrmsca8kBHtY1tTek1ce+XkK/H0fzodBKcilM/qXjTyumMHPAzVAxKZfSvGC+28/pXyQzhOEyxZfw7giCiWA=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.821.0", "", { "dependencies": { "@aws-sdk/core": "3.821.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-e18ucfqKB3ICNj5RP/FEdvUfhVK6E9MALOsl8pKP13mwegug46p/1BsZWACD5n+Zf9ViiiHxIO7td03zQixfwA=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.821.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.821.0", "@aws-sdk/core": "3.821.0", "@aws-sdk/token-providers": "3.821.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-Dt+pheBLom4O/egO4L75/72k9C1qtUOLl0F0h6lmqZe4Mvhz+wDtjoO/MdGC/P1q0kcIX/bBKr0NQ3cIvAH8pA=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.821.0", "", { "dependencies": { "@aws-sdk/core": "3.821.0", "@aws-sdk/nested-clients": "3.821.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-FF5wnRJkxSQaCVVvWNv53K1MhTMgH8d+O+MHTbkv51gVIgVATrtfFQMKBLcEAxzXrgAliIO3LiNv+1TqqBZ+BA=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.0.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.1.3", "@smithy/property-provider": "^4.0.4", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "tslib": "^2.6.2" } }, "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@smithy/property-provider": ["@smithy/property-provider@4.0.4", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.0.4", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.4", "", { "dependencies": { "@smithy/types": "^4.3.1", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/middleware-endpoint/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.0.4", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.0.5", "", { "dependencies": { "@smithy/types": "^4.3.1" } }, "sha512-LvcfhrnCBvCmTee81pRlh1F39yTS/+kYleVeLCwNtkY8wtGg8V/ca9rbZZvYIl8OjlMtL6KIjaiL/lgVqHD2nA=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/middleware-retry/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@4.0.4", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/node-config-provider/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.0.4", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.0.4", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.4", "", { "dependencies": { "@smithy/types": "^4.3.1", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.0.4", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/util-defaults-mode-browser/@smithy/property-provider": ["@smithy/property-provider@4.0.4", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/util-defaults-mode-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.0.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.1.3", "@smithy/property-provider": "^4.0.4", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "tslib": "^2.6.2" } }, "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/util-defaults-mode-node/@smithy/property-provider": ["@smithy/property-provider@4.0.4", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@smithy/util-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.0.5", "", { "dependencies": { "@smithy/types": "^4.3.1" } }, "sha512-LvcfhrnCBvCmTee81pRlh1F39yTS/+kYleVeLCwNtkY8wtGg8V/ca9rbZZvYIl8OjlMtL6KIjaiL/lgVqHD2nA=="],
|
||||||
|
|
||||||
"@aws-sdk/client-rds-data/@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-o+VRiwC2cgmk/WFV0jaETGOtX16VNPp2bSQEzu0whbReqE1BMqsP2ami2Vi3cbGVdKu1kq9gQkDAGKbt0WOHAQ=="],
|
"@aws-sdk/client-rds-data/@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-o+VRiwC2cgmk/WFV0jaETGOtX16VNPp2bSQEzu0whbReqE1BMqsP2ami2Vi3cbGVdKu1kq9gQkDAGKbt0WOHAQ=="],
|
||||||
|
|
||||||
"@aws-sdk/client-rds-data/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.0.1", "", { "dependencies": { "@smithy/is-array-buffer": "^4.0.0", "@smithy/protocol-http": "^5.0.1", "@smithy/types": "^4.1.0", "@smithy/util-hex-encoding": "^4.0.0", "@smithy/util-middleware": "^4.0.1", "@smithy/util-uri-escape": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-nCe6fQ+ppm1bQuw5iKoeJ0MJfz2os7Ic3GBjOkLOPtavbD1ONoyE3ygjBfz2ythFWm4YnRm6OxW+8p/m9uCoIA=="],
|
"@aws-sdk/client-rds-data/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.0.1", "", { "dependencies": { "@smithy/is-array-buffer": "^4.0.0", "@smithy/protocol-http": "^5.0.1", "@smithy/types": "^4.1.0", "@smithy/util-hex-encoding": "^4.0.0", "@smithy/util-middleware": "^4.0.1", "@smithy/util-uri-escape": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-nCe6fQ+ppm1bQuw5iKoeJ0MJfz2os7Ic3GBjOkLOPtavbD1ONoyE3ygjBfz2ythFWm4YnRm6OxW+8p/m9uCoIA=="],
|
||||||
@@ -5761,6 +6251,10 @@
|
|||||||
|
|
||||||
"@aws-sdk/client-s3/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.1.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.0.0", "@smithy/protocol-http": "^5.1.0", "@smithy/types": "^4.2.0", "@smithy/util-hex-encoding": "^4.0.0", "@smithy/util-middleware": "^4.0.2", "@smithy/util-uri-escape": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-4t5WX60sL3zGJF/CtZsUQTs3UrZEDO2P7pEaElrekbLqkWPYkgqNW1oeiNYC6xXifBnT9dVBOnNQRvOE9riU9w=="],
|
"@aws-sdk/client-s3/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.1.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.0.0", "@smithy/protocol-http": "^5.1.0", "@smithy/types": "^4.2.0", "@smithy/util-hex-encoding": "^4.0.0", "@smithy/util-middleware": "^4.0.2", "@smithy/util-uri-escape": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-4t5WX60sL3zGJF/CtZsUQTs3UrZEDO2P7pEaElrekbLqkWPYkgqNW1oeiNYC6xXifBnT9dVBOnNQRvOE9riU9w=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-s3/@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.0.2", "", { "dependencies": { "@smithy/eventstream-codec": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-St8h9JqzvnbB52FtckiHPN4U/cnXcarMniXRXTKn0r4b4XesZOGiAyUdj1aXbqqn1icSqBlzzUsCl6nPB018ng=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-s3/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.0.2", "", { "dependencies": { "@smithy/eventstream-codec": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-St8h9JqzvnbB52FtckiHPN4U/cnXcarMniXRXTKn0r4b4XesZOGiAyUdj1aXbqqn1icSqBlzzUsCl6nPB018ng=="],
|
||||||
|
|
||||||
"@aws-sdk/client-s3/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.0.3", "", { "dependencies": { "@smithy/types": "^4.2.0" } }, "sha512-FTbcajmltovWMjj3tksDQdD23b2w6gH+A0DYA1Yz3iSpjDj8fmkwy62UnXcWMy4d5YoMoSyLFHMfkEVEzbiN8Q=="],
|
"@aws-sdk/client-s3/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.0.3", "", { "dependencies": { "@smithy/types": "^4.2.0" } }, "sha512-FTbcajmltovWMjj3tksDQdD23b2w6gH+A0DYA1Yz3iSpjDj8fmkwy62UnXcWMy4d5YoMoSyLFHMfkEVEzbiN8Q=="],
|
||||||
|
|
||||||
"@aws-sdk/client-s3/@smithy/middleware-retry/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
"@aws-sdk/client-s3/@smithy/middleware-retry/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||||
@@ -6163,6 +6657,12 @@
|
|||||||
|
|
||||||
"@nuxtjs/tailwindcss/c12/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
|
"@nuxtjs/tailwindcss/c12/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/auto-instrumentations-node/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/auto-instrumentations-node/@opentelemetry/instrumentation-grpc/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/auto-instrumentations-node/@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
"@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
"@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
"@opentelemetry/exporter-logs-otlp-http/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
"@opentelemetry/exporter-logs-otlp-http/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
@@ -6207,6 +6707,12 @@
|
|||||||
|
|
||||||
"@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/sdk-trace-base/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
"@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/sdk-trace-base/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-fs/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-pino/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-undici/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
"@opentelemetry/otlp-exporter-base/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
"@opentelemetry/otlp-exporter-base/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
"@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
"@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
@@ -6227,6 +6733,8 @@
|
|||||||
|
|
||||||
"@opentelemetry/sdk-node/@opentelemetry/sdk-trace-node/@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.0.0", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-IEkJGzK1A9v3/EHjXh3s2IiFc6L4jfK+lNgKVgUjeUJQRRhnVFMIO3TAvKwonm9O1HebCuoOt98v8bZW7oVQHA=="],
|
"@opentelemetry/sdk-node/@opentelemetry/sdk-trace-node/@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.0.0", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-IEkJGzK1A9v3/EHjXh3s2IiFc6L4jfK+lNgKVgUjeUJQRRhnVFMIO3TAvKwonm9O1HebCuoOt98v8bZW7oVQHA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/sql-common/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
|
|
||||||
"@rocicorp/zero/@opentelemetry/resources/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
"@rocicorp/zero/@opentelemetry/resources/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||||
|
|
||||||
"@rocicorp/zero/@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
"@rocicorp/zero/@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="],
|
||||||
@@ -6253,12 +6761,22 @@
|
|||||||
|
|
||||||
"@types/bun/bun-types/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
"@types/bun/bun-types/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
||||||
|
|
||||||
|
"@types/bunyan/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||||
|
|
||||||
"@types/cacheable-request/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
"@types/cacheable-request/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||||
|
|
||||||
|
"@types/connect/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||||
|
|
||||||
"@types/keyv/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
"@types/keyv/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||||
|
|
||||||
|
"@types/memcached/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||||
|
|
||||||
|
"@types/mysql/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||||
|
|
||||||
"@types/node-fetch/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
"@types/node-fetch/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||||
|
|
||||||
|
"@types/pg/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||||
|
|
||||||
"@types/pngjs/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
"@types/pngjs/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||||
|
|
||||||
"@types/qrcode/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
"@types/qrcode/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||||
@@ -6271,8 +6789,12 @@
|
|||||||
|
|
||||||
"@types/steamcommunity/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
"@types/steamcommunity/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||||
|
|
||||||
|
"@types/tedious/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||||
|
|
||||||
"@types/ws/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
"@types/ws/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||||
|
|
||||||
|
"@types/xml2js/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
||||||
|
|
||||||
"@vanilla-extract/integration/vite/esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="],
|
"@vanilla-extract/integration/vite/esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="],
|
||||||
@@ -6395,11 +6917,13 @@
|
|||||||
|
|
||||||
"eslint-plugin-jsdoc/espree/eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="],
|
"eslint-plugin-jsdoc/espree/eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="],
|
||||||
|
|
||||||
"eslint-plugin-qwik/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.28.0", "", { "dependencies": { "@typescript-eslint/types": "8.28.0", "@typescript-eslint/visitor-keys": "8.28.0" } }, "sha512-u2oITX3BJwzWCapoZ/pXw6BCOl8rJP4Ij/3wPoGvY8XwvXflOzd1kLrDUUUAIEdJSFh+ASwdTHqtan9xSg8buw=="],
|
"eslint-plugin-qwik/@typescript-eslint/utils/@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=="],
|
||||||
|
|
||||||
"eslint-plugin-qwik/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.28.0", "", {}, "sha512-bn4WS1bkKEjx7HqiwG2JNB3YJdC1q6Ue7GyGlwPHyt0TnVq6TtD/hiOdTZt71sq0s7UzqBFXD8t8o2e63tXgwA=="],
|
"eslint-plugin-qwik/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.33.0", "", { "dependencies": { "@typescript-eslint/types": "8.33.0", "@typescript-eslint/visitor-keys": "8.33.0" } }, "sha512-LMi/oqrzpqxyO72ltP+dBSP6V0xiUb4saY7WLtxSfiNEBI8m321LLVFU9/QDJxjDQG9/tjSqKz/E3380TEqSTw=="],
|
||||||
|
|
||||||
"eslint-plugin-qwik/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.28.0", "", { "dependencies": { "@typescript-eslint/types": "8.28.0", "@typescript-eslint/visitor-keys": "8.28.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-H74nHEeBGeklctAVUvmDkxB1mk+PAZ9FiOMPFncdqeRBXxk1lWSYraHw8V12b7aa6Sg9HOBNbGdSHobBPuQSuA=="],
|
"eslint-plugin-qwik/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.33.0", "", {}, "sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg=="],
|
||||||
|
|
||||||
|
"eslint-plugin-qwik/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.33.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.33.0", "@typescript-eslint/tsconfig-utils": "8.33.0", "@typescript-eslint/types": "8.33.0", "@typescript-eslint/visitor-keys": "8.33.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-vegY4FQoB6jL97Tu/lWRsAiUUp8qJTqzAmENH2k59SJhw0Th1oszb9Idq/FyyONLuNqT1OADJPXfyUNOR8SzAQ=="],
|
||||||
|
|
||||||
"eslint/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
"eslint/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
||||||
|
|
||||||
@@ -6975,6 +7499,14 @@
|
|||||||
|
|
||||||
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.821.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.821.0", "@aws-sdk/middleware-host-header": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", "@aws-sdk/middleware-user-agent": "3.821.0", "@aws-sdk/region-config-resolver": "3.821.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.821.0", "@aws-sdk/util-user-agent-browser": "3.821.0", "@aws-sdk/util-user-agent-node": "3.821.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.5.1", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/hash-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", "@smithy/middleware-endpoint": "^4.1.9", "@smithy/middleware-retry": "^4.1.10", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", "@smithy/smithy-client": "^4.4.1", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.17", "@smithy/util-defaults-mode-node": "^4.0.17", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.5", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-2IuHcUsWw44ftSEDYU4dvktTEqgyDvkOcfpoGC/UmT4Qo6TVCP3U5tWEGpNK9nN+7nLvekruxxG/jaMt5/oWVw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.821.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.821.0", "@aws-sdk/middleware-host-header": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", "@aws-sdk/middleware-user-agent": "3.821.0", "@aws-sdk/region-config-resolver": "3.821.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.821.0", "@aws-sdk/util-user-agent-browser": "3.821.0", "@aws-sdk/util-user-agent-node": "3.821.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.5.1", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/hash-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", "@smithy/middleware-endpoint": "^4.1.9", "@smithy/middleware-retry": "^4.1.10", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", "@smithy/smithy-client": "^4.4.1", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.17", "@smithy/util-defaults-mode-node": "^4.0.17", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.5", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-aDEBZUKUd/+Tvudi0d9KQlqt2OW2P27LATZX0jkNC8yVk4145bAPS04EYoqdKLuyUn/U33DibEOgKUpxZB12jQ=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.821.0", "", { "dependencies": { "@aws-sdk/core": "3.821.0", "@aws-sdk/nested-clients": "3.821.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-qJ7wgKhdxGbPg718zWXbCYKDuSWZNU3TSw64hPRW6FtbZrIyZxObpiTKC6DKwfsVoZZhHEoP/imGykN1OdOTJA=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.821.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.821.0", "@aws-sdk/middleware-host-header": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", "@aws-sdk/middleware-user-agent": "3.821.0", "@aws-sdk/region-config-resolver": "3.821.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.821.0", "@aws-sdk/util-user-agent-browser": "3.821.0", "@aws-sdk/util-user-agent-node": "3.821.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.5.1", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/hash-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", "@smithy/middleware-endpoint": "^4.1.9", "@smithy/middleware-retry": "^4.1.10", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", "@smithy/smithy-client": "^4.4.1", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.17", "@smithy/util-defaults-mode-node": "^4.0.17", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.5", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-2IuHcUsWw44ftSEDYU4dvktTEqgyDvkOcfpoGC/UmT4Qo6TVCP3U5tWEGpNK9nN+7nLvekruxxG/jaMt5/oWVw=="],
|
||||||
|
|
||||||
"@aws-sdk/client-rds-data/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream": ["@smithy/util-stream@4.1.2", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.0.1", "@smithy/node-http-handler": "^4.0.3", "@smithy/types": "^4.1.0", "@smithy/util-base64": "^4.0.0", "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-hex-encoding": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-44PKEqQ303d3rlQuiDpcCcu//hV8sn+u2JBo84dWCE0rvgeiVl0IlLMagbU++o0jCWhYCsHaAt9wZuZqNe05Hw=="],
|
"@aws-sdk/client-rds-data/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream": ["@smithy/util-stream@4.1.2", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.0.1", "@smithy/node-http-handler": "^4.0.3", "@smithy/types": "^4.1.0", "@smithy/util-base64": "^4.0.0", "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-hex-encoding": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-44PKEqQ303d3rlQuiDpcCcu//hV8sn+u2JBo84dWCE0rvgeiVl0IlLMagbU++o0jCWhYCsHaAt9wZuZqNe05Hw=="],
|
||||||
|
|
||||||
"@aws-sdk/client-rds-data/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.758.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.758.0", "@aws-sdk/middleware-host-header": "3.734.0", "@aws-sdk/middleware-logger": "3.734.0", "@aws-sdk/middleware-recursion-detection": "3.734.0", "@aws-sdk/middleware-user-agent": "3.758.0", "@aws-sdk/region-config-resolver": "3.734.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-endpoints": "3.743.0", "@aws-sdk/util-user-agent-browser": "3.734.0", "@aws-sdk/util-user-agent-node": "3.758.0", "@smithy/config-resolver": "^4.0.1", "@smithy/core": "^3.1.5", "@smithy/fetch-http-handler": "^5.0.1", "@smithy/hash-node": "^4.0.1", "@smithy/invalid-dependency": "^4.0.1", "@smithy/middleware-content-length": "^4.0.1", "@smithy/middleware-endpoint": "^4.0.6", "@smithy/middleware-retry": "^4.0.7", "@smithy/middleware-serde": "^4.0.2", "@smithy/middleware-stack": "^4.0.1", "@smithy/node-config-provider": "^4.0.1", "@smithy/node-http-handler": "^4.0.3", "@smithy/protocol-http": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/url-parser": "^4.0.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.7", "@smithy/util-defaults-mode-node": "^4.0.7", "@smithy/util-endpoints": "^3.0.1", "@smithy/util-middleware": "^4.0.1", "@smithy/util-retry": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-YZ5s7PSvyF3Mt2h1EQulCG93uybprNGbBkPmVuy/HMMfbFTt4iL3SbKjxqvOZelm86epFfj7pvK7FliI2WOEcg=="],
|
"@aws-sdk/client-rds-data/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.758.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.758.0", "@aws-sdk/middleware-host-header": "3.734.0", "@aws-sdk/middleware-logger": "3.734.0", "@aws-sdk/middleware-recursion-detection": "3.734.0", "@aws-sdk/middleware-user-agent": "3.758.0", "@aws-sdk/region-config-resolver": "3.734.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-endpoints": "3.743.0", "@aws-sdk/util-user-agent-browser": "3.734.0", "@aws-sdk/util-user-agent-node": "3.758.0", "@smithy/config-resolver": "^4.0.1", "@smithy/core": "^3.1.5", "@smithy/fetch-http-handler": "^5.0.1", "@smithy/hash-node": "^4.0.1", "@smithy/invalid-dependency": "^4.0.1", "@smithy/middleware-content-length": "^4.0.1", "@smithy/middleware-endpoint": "^4.0.6", "@smithy/middleware-retry": "^4.0.7", "@smithy/middleware-serde": "^4.0.2", "@smithy/middleware-stack": "^4.0.1", "@smithy/node-config-provider": "^4.0.1", "@smithy/node-http-handler": "^4.0.3", "@smithy/protocol-http": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/url-parser": "^4.0.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.7", "@smithy/util-defaults-mode-node": "^4.0.7", "@smithy/util-endpoints": "^3.0.1", "@smithy/util-middleware": "^4.0.1", "@smithy/util-retry": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-YZ5s7PSvyF3Mt2h1EQulCG93uybprNGbBkPmVuy/HMMfbFTt4iL3SbKjxqvOZelm86epFfj7pvK7FliI2WOEcg=="],
|
||||||
@@ -6993,6 +7525,10 @@
|
|||||||
|
|
||||||
"@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.806.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.806.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-user-agent": "3.806.0", "@aws-sdk/region-config-resolver": "3.806.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.806.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.806.0", "@smithy/config-resolver": "^4.1.1", "@smithy/core": "^3.3.1", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.3", "@smithy/middleware-retry": "^4.1.4", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.0", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.3", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.11", "@smithy/util-defaults-mode-node": "^4.0.11", "@smithy/util-endpoints": "^3.0.3", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-ua2gzpfQ9MF8Rny+tOAivowOWWvqEusez2rdcQK8jdBjA1ANd/0xzToSZjZh0ziN8Kl8jOhNnHbQJ0v6dT6+hg=="],
|
"@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.806.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.806.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-user-agent": "3.806.0", "@aws-sdk/region-config-resolver": "3.806.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.806.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.806.0", "@smithy/config-resolver": "^4.1.1", "@smithy/core": "^3.3.1", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.3", "@smithy/middleware-retry": "^4.1.4", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.0", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.3", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.11", "@smithy/util-defaults-mode-node": "^4.0.11", "@smithy/util-endpoints": "^3.0.3", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-ua2gzpfQ9MF8Rny+tOAivowOWWvqEusez2rdcQK8jdBjA1ANd/0xzToSZjZh0ziN8Kl8jOhNnHbQJ0v6dT6+hg=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-s3/@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.0.2", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.2.0", "@smithy/util-hex-encoding": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-p+f2kLSK7ZrXVfskU/f5dzksKTewZk8pJLPvER3aFHPt76C2MxD9vNatSfLzzQSQB4FNO96RK4PSXfhD1TTeMQ=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-s3/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.0.2", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.2.0", "@smithy/util-hex-encoding": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-p+f2kLSK7ZrXVfskU/f5dzksKTewZk8pJLPvER3aFHPt76C2MxD9vNatSfLzzQSQB4FNO96RK4PSXfhD1TTeMQ=="],
|
||||||
|
|
||||||
"@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream": ["@smithy/util-stream@4.1.2", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.0.1", "@smithy/node-http-handler": "^4.0.3", "@smithy/types": "^4.1.0", "@smithy/util-base64": "^4.0.0", "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-hex-encoding": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-44PKEqQ303d3rlQuiDpcCcu//hV8sn+u2JBo84dWCE0rvgeiVl0IlLMagbU++o0jCWhYCsHaAt9wZuZqNe05Hw=="],
|
"@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream": ["@smithy/util-stream@4.1.2", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.0.1", "@smithy/node-http-handler": "^4.0.3", "@smithy/types": "^4.1.0", "@smithy/util-base64": "^4.0.0", "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-hex-encoding": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-44PKEqQ303d3rlQuiDpcCcu//hV8sn+u2JBo84dWCE0rvgeiVl0IlLMagbU++o0jCWhYCsHaAt9wZuZqNe05Hw=="],
|
||||||
|
|
||||||
"@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.758.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.758.0", "@aws-sdk/middleware-host-header": "3.734.0", "@aws-sdk/middleware-logger": "3.734.0", "@aws-sdk/middleware-recursion-detection": "3.734.0", "@aws-sdk/middleware-user-agent": "3.758.0", "@aws-sdk/region-config-resolver": "3.734.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-endpoints": "3.743.0", "@aws-sdk/util-user-agent-browser": "3.734.0", "@aws-sdk/util-user-agent-node": "3.758.0", "@smithy/config-resolver": "^4.0.1", "@smithy/core": "^3.1.5", "@smithy/fetch-http-handler": "^5.0.1", "@smithy/hash-node": "^4.0.1", "@smithy/invalid-dependency": "^4.0.1", "@smithy/middleware-content-length": "^4.0.1", "@smithy/middleware-endpoint": "^4.0.6", "@smithy/middleware-retry": "^4.0.7", "@smithy/middleware-serde": "^4.0.2", "@smithy/middleware-stack": "^4.0.1", "@smithy/node-config-provider": "^4.0.1", "@smithy/node-http-handler": "^4.0.3", "@smithy/protocol-http": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/url-parser": "^4.0.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.7", "@smithy/util-defaults-mode-node": "^4.0.7", "@smithy/util-endpoints": "^3.0.1", "@smithy/util-middleware": "^4.0.1", "@smithy/util-retry": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-YZ5s7PSvyF3Mt2h1EQulCG93uybprNGbBkPmVuy/HMMfbFTt4iL3SbKjxqvOZelm86epFfj7pvK7FliI2WOEcg=="],
|
"@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.758.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.758.0", "@aws-sdk/middleware-host-header": "3.734.0", "@aws-sdk/middleware-logger": "3.734.0", "@aws-sdk/middleware-recursion-detection": "3.734.0", "@aws-sdk/middleware-user-agent": "3.758.0", "@aws-sdk/region-config-resolver": "3.734.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-endpoints": "3.743.0", "@aws-sdk/util-user-agent-browser": "3.734.0", "@aws-sdk/util-user-agent-node": "3.758.0", "@smithy/config-resolver": "^4.0.1", "@smithy/core": "^3.1.5", "@smithy/fetch-http-handler": "^5.0.1", "@smithy/hash-node": "^4.0.1", "@smithy/invalid-dependency": "^4.0.1", "@smithy/middleware-content-length": "^4.0.1", "@smithy/middleware-endpoint": "^4.0.6", "@smithy/middleware-retry": "^4.0.7", "@smithy/middleware-serde": "^4.0.2", "@smithy/middleware-stack": "^4.0.1", "@smithy/node-config-provider": "^4.0.1", "@smithy/node-http-handler": "^4.0.3", "@smithy/protocol-http": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/url-parser": "^4.0.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.7", "@smithy/util-defaults-mode-node": "^4.0.7", "@smithy/util-endpoints": "^3.0.1", "@smithy/util-middleware": "^4.0.1", "@smithy/util-retry": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-YZ5s7PSvyF3Mt2h1EQulCG93uybprNGbBkPmVuy/HMMfbFTt4iL3SbKjxqvOZelm86epFfj7pvK7FliI2WOEcg=="],
|
||||||
@@ -7429,14 +7965,16 @@
|
|||||||
|
|
||||||
"eslint-plugin-import-x/@typescript-eslint/utils/@typescript-eslint/typescript-estree/ts-api-utils": ["ts-api-utils@2.0.1", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w=="],
|
"eslint-plugin-import-x/@typescript-eslint/utils/@typescript-eslint/typescript-estree/ts-api-utils": ["ts-api-utils@2.0.1", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w=="],
|
||||||
|
|
||||||
"eslint-plugin-qwik/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.28.0", "", { "dependencies": { "@typescript-eslint/types": "8.28.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-hbn8SZ8w4u2pRwgQ1GlUrPKE+t2XvcCW5tTRF7j6SMYIuYG37XuzIW44JCZPa36evi0Oy2SnM664BlIaAuQcvg=="],
|
"eslint-plugin-qwik/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.33.0", "", { "dependencies": { "@typescript-eslint/types": "8.33.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-7RW7CMYoskiz5OOGAWjJFxgb7c5UNjTG292gYhWeOAcFmYCtVCSqjqSBj5zMhxbXo2JOW95YYrUWJfU0zrpaGQ=="],
|
||||||
|
|
||||||
"eslint-plugin-qwik/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.28.0", "", { "dependencies": { "@typescript-eslint/types": "8.28.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-hbn8SZ8w4u2pRwgQ1GlUrPKE+t2XvcCW5tTRF7j6SMYIuYG37XuzIW44JCZPa36evi0Oy2SnM664BlIaAuQcvg=="],
|
"eslint-plugin-qwik/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.33.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.33.0", "@typescript-eslint/types": "^8.33.0", "debug": "^4.3.4" } }, "sha512-d1hz0u9l6N+u/gcrk6s6gYdl7/+pp8yHheRTqP6X5hVDKALEaTn8WfGiit7G511yueBEL3OpOEpD+3/MBdoN+A=="],
|
||||||
|
|
||||||
|
"eslint-plugin-qwik/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.33.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-sTkETlbqhEoiFmGr1gsdq5HyVbSOF0145SYDJ/EQmXHtKViCaGvnyLqWFFHtEXoS0J1yU8Wyou2UGmgW88fEug=="],
|
||||||
|
|
||||||
|
"eslint-plugin-qwik/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.33.0", "", { "dependencies": { "@typescript-eslint/types": "8.33.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-7RW7CMYoskiz5OOGAWjJFxgb7c5UNjTG292gYhWeOAcFmYCtVCSqjqSBj5zMhxbXo2JOW95YYrUWJfU0zrpaGQ=="],
|
||||||
|
|
||||||
"eslint-plugin-qwik/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
"eslint-plugin-qwik/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||||
|
|
||||||
"eslint-plugin-qwik/@typescript-eslint/utils/@typescript-eslint/typescript-estree/ts-api-utils": ["ts-api-utils@2.0.1", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w=="],
|
|
||||||
|
|
||||||
"pkg-dir/find-up/locate-path/p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="],
|
"pkg-dir/find-up/locate-path/p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="],
|
||||||
|
|
||||||
"read-pkg-up/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
"read-pkg-up/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||||
@@ -7539,6 +8077,8 @@
|
|||||||
|
|
||||||
"yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
"yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.821.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.821.0", "@aws-sdk/middleware-host-header": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", "@aws-sdk/middleware-user-agent": "3.821.0", "@aws-sdk/region-config-resolver": "3.821.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.821.0", "@aws-sdk/util-user-agent-browser": "3.821.0", "@aws-sdk/util-user-agent-node": "3.821.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.5.1", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/hash-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", "@smithy/middleware-endpoint": "^4.1.9", "@smithy/middleware-retry": "^4.1.10", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", "@smithy/smithy-client": "^4.4.1", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.17", "@smithy/util-defaults-mode-node": "^4.0.17", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.5", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-2IuHcUsWw44ftSEDYU4dvktTEqgyDvkOcfpoGC/UmT4Qo6TVCP3U5tWEGpNK9nN+7nLvekruxxG/jaMt5/oWVw=="],
|
||||||
|
|
||||||
"@aws-sdk/client-rds-data/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.758.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.758.0", "@aws-sdk/middleware-host-header": "3.734.0", "@aws-sdk/middleware-logger": "3.734.0", "@aws-sdk/middleware-recursion-detection": "3.734.0", "@aws-sdk/middleware-user-agent": "3.758.0", "@aws-sdk/region-config-resolver": "3.734.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-endpoints": "3.743.0", "@aws-sdk/util-user-agent-browser": "3.734.0", "@aws-sdk/util-user-agent-node": "3.758.0", "@smithy/config-resolver": "^4.0.1", "@smithy/core": "^3.1.5", "@smithy/fetch-http-handler": "^5.0.1", "@smithy/hash-node": "^4.0.1", "@smithy/invalid-dependency": "^4.0.1", "@smithy/middleware-content-length": "^4.0.1", "@smithy/middleware-endpoint": "^4.0.6", "@smithy/middleware-retry": "^4.0.7", "@smithy/middleware-serde": "^4.0.2", "@smithy/middleware-stack": "^4.0.1", "@smithy/node-config-provider": "^4.0.1", "@smithy/node-http-handler": "^4.0.3", "@smithy/protocol-http": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/url-parser": "^4.0.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.7", "@smithy/util-defaults-mode-node": "^4.0.7", "@smithy/util-endpoints": "^3.0.1", "@smithy/util-middleware": "^4.0.1", "@smithy/util-retry": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-YZ5s7PSvyF3Mt2h1EQulCG93uybprNGbBkPmVuy/HMMfbFTt4iL3SbKjxqvOZelm86epFfj7pvK7FliI2WOEcg=="],
|
"@aws-sdk/client-rds-data/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.758.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.758.0", "@aws-sdk/middleware-host-header": "3.734.0", "@aws-sdk/middleware-logger": "3.734.0", "@aws-sdk/middleware-recursion-detection": "3.734.0", "@aws-sdk/middleware-user-agent": "3.758.0", "@aws-sdk/region-config-resolver": "3.734.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-endpoints": "3.743.0", "@aws-sdk/util-user-agent-browser": "3.734.0", "@aws-sdk/util-user-agent-node": "3.758.0", "@smithy/config-resolver": "^4.0.1", "@smithy/core": "^3.1.5", "@smithy/fetch-http-handler": "^5.0.1", "@smithy/hash-node": "^4.0.1", "@smithy/invalid-dependency": "^4.0.1", "@smithy/middleware-content-length": "^4.0.1", "@smithy/middleware-endpoint": "^4.0.6", "@smithy/middleware-retry": "^4.0.7", "@smithy/middleware-serde": "^4.0.2", "@smithy/middleware-stack": "^4.0.1", "@smithy/node-config-provider": "^4.0.1", "@smithy/node-http-handler": "^4.0.3", "@smithy/protocol-http": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/url-parser": "^4.0.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.7", "@smithy/util-defaults-mode-node": "^4.0.7", "@smithy/util-endpoints": "^3.0.1", "@smithy/util-middleware": "^4.0.1", "@smithy/util-retry": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-YZ5s7PSvyF3Mt2h1EQulCG93uybprNGbBkPmVuy/HMMfbFTt4iL3SbKjxqvOZelm86epFfj7pvK7FliI2WOEcg=="],
|
||||||
|
|
||||||
"@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.806.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.806.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-user-agent": "3.806.0", "@aws-sdk/region-config-resolver": "3.806.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.806.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.806.0", "@smithy/config-resolver": "^4.1.1", "@smithy/core": "^3.3.1", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.3", "@smithy/middleware-retry": "^4.1.4", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.0", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.3", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.11", "@smithy/util-defaults-mode-node": "^4.0.11", "@smithy/util-endpoints": "^3.0.3", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-ua2gzpfQ9MF8Rny+tOAivowOWWvqEusez2rdcQK8jdBjA1ANd/0xzToSZjZh0ziN8Kl8jOhNnHbQJ0v6dT6+hg=="],
|
"@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.806.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.806.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-user-agent": "3.806.0", "@aws-sdk/region-config-resolver": "3.806.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.806.0", "@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-node": "3.806.0", "@smithy/config-resolver": "^4.1.1", "@smithy/core": "^3.3.1", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.3", "@smithy/middleware-retry": "^4.1.4", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.0", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.3", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.11", "@smithy/util-defaults-mode-node": "^4.0.11", "@smithy/util-endpoints": "^3.0.3", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-ua2gzpfQ9MF8Rny+tOAivowOWWvqEusez2rdcQK8jdBjA1ANd/0xzToSZjZh0ziN8Kl8jOhNnHbQJ0v6dT6+hg=="],
|
||||||
|
|||||||
@@ -10,21 +10,19 @@ WORKDIR /relay
|
|||||||
# TODO: Switch running layer to just alpine (doesn't need golang dev stack)
|
# TODO: Switch running layer to just alpine (doesn't need golang dev stack)
|
||||||
|
|
||||||
# ENV flags
|
# ENV flags
|
||||||
|
ENV REGEN_IDENTITY=false
|
||||||
ENV VERBOSE=false
|
ENV VERBOSE=false
|
||||||
ENV DEBUG=false
|
ENV DEBUG=false
|
||||||
ENV ENDPOINT_PORT=8088
|
ENV ENDPOINT_PORT=8088
|
||||||
ENV MESH_PORT=8089
|
ENV WEBRTC_UDP_START=0
|
||||||
ENV WEBRTC_UDP_START=10000
|
ENV WEBRTC_UDP_END=0
|
||||||
ENV WEBRTC_UDP_END=20000
|
|
||||||
ENV STUN_SERVER="stun.l.google.com:19302"
|
ENV STUN_SERVER="stun.l.google.com:19302"
|
||||||
ENV WEBRTC_UDP_MUX=8088
|
ENV WEBRTC_UDP_MUX=8088
|
||||||
ENV WEBRTC_NAT_IPS=""
|
ENV WEBRTC_NAT_IPS=""
|
||||||
ENV AUTO_ADD_LOCAL_IP=true
|
ENV AUTO_ADD_LOCAL_IP=true
|
||||||
ENV TLS_CERT=""
|
ENV PERSIST_DIR="./persist-data"
|
||||||
ENV TLS_KEY=""
|
|
||||||
|
|
||||||
EXPOSE $ENDPOINT_PORT
|
EXPOSE $ENDPOINT_PORT
|
||||||
EXPOSE $MESH_PORT
|
|
||||||
EXPOSE $WEBRTC_UDP_START-$WEBRTC_UDP_END/udp
|
EXPOSE $WEBRTC_UDP_START-$WEBRTC_UDP_END/udp
|
||||||
EXPOSE $WEBRTC_UDP_MUX/udp
|
EXPOSE $WEBRTC_UDP_MUX/udp
|
||||||
|
|
||||||
|
|||||||
@@ -168,8 +168,7 @@ ENV USER="nestri" \
|
|||||||
USER_PWD="nestri1234" \
|
USER_PWD="nestri1234" \
|
||||||
XDG_RUNTIME_DIR=/run/user/1000 \
|
XDG_RUNTIME_DIR=/run/user/1000 \
|
||||||
HOME=/home/nestri \
|
HOME=/home/nestri \
|
||||||
NVIDIA_DRIVER_CAPABILITIES=all \
|
NVIDIA_DRIVER_CAPABILITIES=all
|
||||||
NVIDIA_VISIBLE_DEVICES=all
|
|
||||||
|
|
||||||
RUN mkdir -p /home/${USER} && \
|
RUN mkdir -p /home/${USER} && \
|
||||||
groupadd -g ${GID} ${USER} && \
|
groupadd -g ${GID} ${USER} && \
|
||||||
|
|||||||
150
infra/api.ts
150
infra/api.ts
@@ -1,21 +1,28 @@
|
|||||||
import { bus } from "./bus";
|
import { bus } from "./bus";
|
||||||
|
import { vpc } from "./vpc";
|
||||||
import { auth } from "./auth";
|
import { auth } from "./auth";
|
||||||
import { domain } from "./dns";
|
import { domain } from "./dns";
|
||||||
import { cluster } from "./cluster";
|
import { secret } from "./secret";
|
||||||
import { postgres } from "./postgres";
|
import { postgres } from "./postgres";
|
||||||
import { LibraryQueue } from "./steam";
|
|
||||||
import { secret, steamEncryptionKey } from "./secret";
|
|
||||||
|
|
||||||
export const apiService = new sst.aws.Service("Api", {
|
const urls = new sst.Linkable("Urls", {
|
||||||
cluster,
|
properties: {
|
||||||
cpu: $app.stage === "production" ? "2 vCPU" : undefined,
|
api: `https://api.${domain}`,
|
||||||
memory: $app.stage === "production" ? "4 GB" : undefined,
|
auth: `https://auth.${domain}`,
|
||||||
|
site: $dev ? "http://localhost:3000" : `https://console.${domain}`,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const apiFn = new sst.aws.Function("ApiFn", {
|
||||||
|
vpc,
|
||||||
|
handler: "packages/functions/src/api/index.handler",
|
||||||
|
streaming: !$dev,
|
||||||
link: [
|
link: [
|
||||||
bus,
|
bus,
|
||||||
|
urls,
|
||||||
auth,
|
auth,
|
||||||
postgres,
|
postgres,
|
||||||
LibraryQueue,
|
secret.SteamApiKey,
|
||||||
steamEncryptionKey,
|
|
||||||
secret.PolarSecret,
|
secret.PolarSecret,
|
||||||
secret.PolarWebhookSecret,
|
secret.PolarWebhookSecret,
|
||||||
secret.NestriFamilyMonthly,
|
secret.NestriFamilyMonthly,
|
||||||
@@ -24,73 +31,90 @@ export const apiService = new sst.aws.Service("Api", {
|
|||||||
secret.NestriProMonthly,
|
secret.NestriProMonthly,
|
||||||
secret.NestriProYearly,
|
secret.NestriProYearly,
|
||||||
],
|
],
|
||||||
command: ["bun", "run", "./src/api/index.ts"],
|
url: true,
|
||||||
image: {
|
});
|
||||||
dockerfile: "packages/functions/Containerfile",
|
|
||||||
|
const provider = new aws.Provider("UsEast1", { region: "us-east-1" });
|
||||||
|
|
||||||
|
const webAcl = new aws.wafv2.WebAcl(
|
||||||
|
"ApiWaf",
|
||||||
|
{
|
||||||
|
scope: "CLOUDFRONT",
|
||||||
|
defaultAction: {
|
||||||
|
allow: {},
|
||||||
|
},
|
||||||
|
visibilityConfig: {
|
||||||
|
cloudwatchMetricsEnabled: true,
|
||||||
|
metricName: "api-rate-limit-metric",
|
||||||
|
sampledRequestsEnabled: true,
|
||||||
},
|
},
|
||||||
loadBalancer: {
|
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
listen: "80/http",
|
name: "rate-limit-rule",
|
||||||
forward: "3001/http",
|
priority: 1,
|
||||||
|
action: {
|
||||||
|
block: {
|
||||||
|
customResponse: {
|
||||||
|
responseCode: 429,
|
||||||
|
customResponseBodyKey: "rate-limit-response",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
statement: {
|
||||||
|
rateBasedStatement: {
|
||||||
|
limit: 2 * 60, // 2 rps per authorization header
|
||||||
|
evaluationWindowSec: 60,
|
||||||
|
aggregateKeyType: "CUSTOM_KEYS",
|
||||||
|
customKeys: [
|
||||||
|
{
|
||||||
|
header: {
|
||||||
|
name: "Authorization",
|
||||||
|
textTransformations: [{ priority: 0, type: "NONE" }],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
dev: {
|
|
||||||
url: "http://localhost:3001",
|
|
||||||
command: "bun dev:api",
|
|
||||||
directory: "packages/functions",
|
|
||||||
},
|
},
|
||||||
scaling:
|
visibilityConfig: {
|
||||||
$app.stage === "production"
|
cloudwatchMetricsEnabled: true,
|
||||||
? {
|
metricName: "rate-limit-rule-metric",
|
||||||
min: 2,
|
sampledRequestsEnabled: true,
|
||||||
max: 10,
|
},
|
||||||
}
|
},
|
||||||
: undefined,
|
],
|
||||||
// For persisting actor state
|
customResponseBodies: [
|
||||||
transform: {
|
|
||||||
taskDefinition: (args) => {
|
|
||||||
const volumes = $output(args.volumes).apply(v => {
|
|
||||||
const next = [...v, {
|
|
||||||
name: "shared-tmp",
|
|
||||||
dockerVolumeConfiguration: {
|
|
||||||
scope: "shared",
|
|
||||||
driver: "local"
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
|
|
||||||
return next;
|
|
||||||
})
|
|
||||||
|
|
||||||
// "containerDefinitions" is a JSON string, parse first
|
|
||||||
let containers = $jsonParse(args.containerDefinitions);
|
|
||||||
|
|
||||||
containers = containers.apply((containerDefinitions) => {
|
|
||||||
containerDefinitions[0].mountPoints = [
|
|
||||||
...(containerDefinitions[0].mountPoints ?? []),
|
|
||||||
{
|
{
|
||||||
sourceVolume: "shared-tmp",
|
key: "rate-limit-response",
|
||||||
containerPath: "/tmp"
|
content: JSON.stringify({
|
||||||
|
type: "rate_limit",
|
||||||
|
code: "too_many_requests",
|
||||||
|
message: "Rate limit exceeded. Please try again later.",
|
||||||
|
}),
|
||||||
|
contentType: "APPLICATION_JSON",
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
return containerDefinitions;
|
},
|
||||||
});
|
{ provider },
|
||||||
|
);
|
||||||
|
|
||||||
args.volumes = volumes
|
export const api = new sst.aws.Router("Api", {
|
||||||
args.containerDefinitions = $jsonStringify(containers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
export const api = !$dev ? new sst.aws.Router("ApiRoute", {
|
|
||||||
routes: {
|
routes: {
|
||||||
// I think api.url should work all the same
|
"/*": apiFn.url,
|
||||||
"/*": apiService.nodes.loadBalancer.dnsName,
|
|
||||||
},
|
},
|
||||||
domain: {
|
domain: {
|
||||||
name: "api." + domain,
|
name: "api." + domain,
|
||||||
dns: sst.cloudflare.dns(),
|
dns: sst.cloudflare.dns(),
|
||||||
},
|
},
|
||||||
}) : apiService
|
transform: {
|
||||||
|
cdn(args) {
|
||||||
|
if (!args.transform) {
|
||||||
|
args.transform = {
|
||||||
|
distribution: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
args.transform!.distribution = {
|
||||||
|
webAclId: webAcl.arn,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,99 +1,32 @@
|
|||||||
import { bus } from "./bus";
|
import { bus } from "./bus";
|
||||||
|
import { vpc } from "./vpc";
|
||||||
import { domain } from "./dns";
|
import { domain } from "./dns";
|
||||||
import { cluster } from "./cluster";
|
import { secret } from "./secret";
|
||||||
import { postgres } from "./postgres";
|
import { postgres } from "./postgres";
|
||||||
import { secret, steamEncryptionKey } from "./secret";
|
|
||||||
|
|
||||||
export const authService = new sst.aws.Service("Auth", {
|
export const auth = new sst.aws.Auth("Auth", {
|
||||||
cluster,
|
authorizer: {
|
||||||
cpu: $app.stage === "production" ? "1 vCPU" : undefined,
|
vpc,
|
||||||
memory: $app.stage === "production" ? "2 GB" : undefined,
|
|
||||||
command: ["bun", "run", "./src/auth/index.ts"],
|
|
||||||
link: [
|
link: [
|
||||||
bus,
|
bus,
|
||||||
postgres,
|
postgres,
|
||||||
secret.PolarSecret,
|
secret.PolarSecret,
|
||||||
steamEncryptionKey,
|
|
||||||
secret.GithubClientID,
|
secret.GithubClientID,
|
||||||
secret.DiscordClientID,
|
secret.DiscordClientID,
|
||||||
secret.GithubClientSecret,
|
secret.GithubClientSecret,
|
||||||
secret.DiscordClientSecret,
|
secret.DiscordClientSecret,
|
||||||
],
|
],
|
||||||
image: {
|
|
||||||
dockerfile: "packages/functions/Containerfile",
|
|
||||||
},
|
|
||||||
environment: {
|
|
||||||
NO_COLOR: "1",
|
|
||||||
STORAGE: "/tmp/persist.json"
|
|
||||||
},
|
|
||||||
loadBalancer: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
listen: "80/http",
|
|
||||||
forward: "3002/http",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
permissions: [
|
permissions: [
|
||||||
{
|
{
|
||||||
actions: ["ses:SendEmail"],
|
actions: ["ses:SendEmail"],
|
||||||
resources: ["*"],
|
resources: ["*"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
dev: {
|
handler: "packages/functions/src/auth/index.handler",
|
||||||
command: "bun dev:auth",
|
|
||||||
directory: "packages/functions",
|
|
||||||
url: "http://localhost:3002",
|
|
||||||
},
|
|
||||||
scaling:
|
|
||||||
$app.stage === "production"
|
|
||||||
? {
|
|
||||||
min: 2,
|
|
||||||
max: 10,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
//For temporarily persisting the persist.json
|
|
||||||
transform: {
|
|
||||||
taskDefinition: (args) => {
|
|
||||||
const volumes = $output(args.volumes).apply(v => {
|
|
||||||
const next = [...v, {
|
|
||||||
name: "shared-tmp",
|
|
||||||
dockerVolumeConfiguration: {
|
|
||||||
scope: "shared",
|
|
||||||
driver: "local"
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
|
|
||||||
return next;
|
|
||||||
})
|
|
||||||
|
|
||||||
// "containerDefinitions" is a JSON string, parse first
|
|
||||||
let containers = $jsonParse(args.containerDefinitions);
|
|
||||||
|
|
||||||
containers = containers.apply((containerDefinitions) => {
|
|
||||||
containerDefinitions[0].mountPoints = [
|
|
||||||
...(containerDefinitions[0].mountPoints ?? []),
|
|
||||||
{
|
|
||||||
sourceVolume: "shared-tmp",
|
|
||||||
containerPath: "/tmp"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
return containerDefinitions;
|
|
||||||
});
|
|
||||||
|
|
||||||
args.volumes = volumes
|
|
||||||
args.containerDefinitions = $jsonStringify(containers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export const auth = !$dev ? new sst.aws.Router("AuthRoute", {
|
|
||||||
routes: {
|
|
||||||
// I think auth.url should work all the same
|
|
||||||
"/*": authService.nodes.loadBalancer.dnsName,
|
|
||||||
},
|
},
|
||||||
domain: {
|
domain: {
|
||||||
name: "auth." + domain,
|
name: "auth." + domain,
|
||||||
dns: sst.cloudflare.dns(),
|
dns: sst.cloudflare.dns(),
|
||||||
},
|
},
|
||||||
}) : authService
|
forceUpgrade: "v2",
|
||||||
|
});
|
||||||
60
infra/bus.ts
60
infra/bus.ts
@@ -1,26 +1,70 @@
|
|||||||
import { vpc } from "./vpc";
|
import { vpc } from "./vpc";
|
||||||
|
import { secret } from "./secret";
|
||||||
import { storage } from "./storage";
|
import { storage } from "./storage";
|
||||||
// import { email } from "./email";
|
|
||||||
import { postgres } from "./postgres";
|
import { postgres } from "./postgres";
|
||||||
import { steamEncryptionKey } from "./secret";
|
|
||||||
|
export const dlq = new sst.aws.Queue("Dlq");
|
||||||
|
|
||||||
|
export const retryQueue = new sst.aws.Queue("RetryQueue");
|
||||||
|
|
||||||
export const bus = new sst.aws.Bus("Bus");
|
export const bus = new sst.aws.Bus("Bus");
|
||||||
|
|
||||||
bus.subscribe("Event", {
|
export const eventSub = bus.subscribe("Event", {
|
||||||
vpc,
|
vpc,
|
||||||
handler: "packages/functions/src/events/index.handler",
|
handler: "packages/functions/src/events/index.handler",
|
||||||
link: [
|
link: [
|
||||||
// email,
|
// email,
|
||||||
postgres,
|
bus,
|
||||||
storage,
|
storage,
|
||||||
steamEncryptionKey
|
postgres,
|
||||||
|
retryQueue,
|
||||||
|
secret.PolarSecret,
|
||||||
|
secret.SteamApiKey
|
||||||
],
|
],
|
||||||
timeout: "10 minutes",
|
environment: {
|
||||||
|
RETRIES: "2",
|
||||||
|
},
|
||||||
memory: "3002 MB",// For faster processing of large(r) images
|
memory: "3002 MB",// For faster processing of large(r) images
|
||||||
|
timeout: "10 minutes",
|
||||||
|
});
|
||||||
|
|
||||||
|
new aws.lambda.FunctionEventInvokeConfig("EventConfig", {
|
||||||
|
functionName: $resolve([eventSub.nodes.function.name]).apply(
|
||||||
|
([name]) => name,
|
||||||
|
),
|
||||||
|
maximumRetryAttempts: 1,
|
||||||
|
destinationConfig: {
|
||||||
|
onFailure: {
|
||||||
|
destination: retryQueue.arn,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
retryQueue.subscribe({
|
||||||
|
vpc,
|
||||||
|
handler: "packages/functions/src/queues/retry.handler",
|
||||||
|
timeout: "30 seconds",
|
||||||
|
environment: {
|
||||||
|
RETRIER_QUEUE_URL: retryQueue.url,
|
||||||
|
},
|
||||||
|
link: [
|
||||||
|
dlq,
|
||||||
|
retryQueue,
|
||||||
|
eventSub.nodes.function,
|
||||||
|
],
|
||||||
permissions: [
|
permissions: [
|
||||||
{
|
{
|
||||||
actions: ["ses:SendEmail"],
|
actions: ["lambda:GetFunction", "lambda:InvokeFunction"],
|
||||||
resources: ["*"],
|
resources: [
|
||||||
|
$interpolate`arn:aws:lambda:${aws.getRegionOutput().name}:${aws.getCallerIdentityOutput().accountId}:function:*`,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
transform: {
|
||||||
|
function: {
|
||||||
|
deadLetterConfig: {
|
||||||
|
targetArn: dlq.arn,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
18
infra/cdn.ts
Normal file
18
infra/cdn.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { domain } from "./dns";
|
||||||
|
import { storage } from "./storage";
|
||||||
|
|
||||||
|
export const cdn = new sst.aws.Router("CDNRouter", {
|
||||||
|
routes: {
|
||||||
|
"/*": {
|
||||||
|
bucket: storage,
|
||||||
|
rewrite: {
|
||||||
|
regex: "^/([a-zA-Z0-9_-]+)$",
|
||||||
|
to: "/public/$1"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
domain: {
|
||||||
|
name: "cdn." + domain,
|
||||||
|
dns: sst.cloudflare.dns()
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,14 +1,9 @@
|
|||||||
import { vpc } from "./vpc";
|
import { vpc } from "./vpc";
|
||||||
import { isPermanentStage } from "./stage";
|
|
||||||
import { steamEncryptionKey } from "./secret";
|
|
||||||
|
|
||||||
// TODO: Add a dev db to use, this will help with running zero locally... and testing it
|
|
||||||
export const postgres = new sst.aws.Aurora("Database", {
|
export const postgres = new sst.aws.Aurora("Database", {
|
||||||
vpc,
|
vpc,
|
||||||
engine: "postgres",
|
engine: "postgres",
|
||||||
scaling: isPermanentStage
|
scaling: {
|
||||||
? undefined
|
|
||||||
: {
|
|
||||||
min: "0 ACU",
|
min: "0 ACU",
|
||||||
max: "1 ACU",
|
max: "1 ACU",
|
||||||
},
|
},
|
||||||
@@ -42,7 +37,7 @@ export const postgres = new sst.aws.Aurora("Database", {
|
|||||||
|
|
||||||
|
|
||||||
new sst.x.DevCommand("Studio", {
|
new sst.x.DevCommand("Studio", {
|
||||||
link: [postgres, steamEncryptionKey],
|
link: [postgres],
|
||||||
dev: {
|
dev: {
|
||||||
command: "bun db:dev studio",
|
command: "bun db:dev studio",
|
||||||
directory: "packages/core",
|
directory: "packages/core",
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { auth } from "./auth";
|
// import { auth } from "./auth";
|
||||||
import { postgres } from "./postgres";
|
import { postgres } from "./postgres";
|
||||||
|
|
||||||
export const device = new sst.aws.Realtime("Realtime", {
|
export const device = new sst.aws.Realtime("Realtime", {
|
||||||
authorizer: {
|
authorizer: {
|
||||||
link: [auth, postgres],
|
link: [ postgres],
|
||||||
handler: "packages/functions/src/realtime/authorizer.handler"
|
handler: "packages/functions/src/realtime/authorizer.handler"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
export const secret = {
|
export const secret = {
|
||||||
PolarSecret: new sst.Secret("PolarSecret", process.env.POLAR_API_KEY),
|
PolarSecret: new sst.Secret("PolarSecret", process.env.POLAR_API_KEY),
|
||||||
|
SteamApiKey: new sst.Secret("SteamApiKey"),
|
||||||
GithubClientID: new sst.Secret("GithubClientID"),
|
GithubClientID: new sst.Secret("GithubClientID"),
|
||||||
DiscordClientID: new sst.Secret("DiscordClientID"),
|
DiscordClientID: new sst.Secret("DiscordClientID"),
|
||||||
PolarWebhookSecret: new sst.Secret("PolarWebhookSecret"),
|
PolarWebhookSecret: new sst.Secret("PolarWebhookSecret"),
|
||||||
@@ -15,16 +16,3 @@ export const secret = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const allSecrets = Object.values(secret);
|
export const allSecrets = Object.values(secret);
|
||||||
|
|
||||||
sst.Linkable.wrap(random.RandomString, (resource) => ({
|
|
||||||
properties: {
|
|
||||||
value: resource.result,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const steamEncryptionKey = new random.RandomString(
|
|
||||||
"SteamEncryptionKey",
|
|
||||||
{
|
|
||||||
length: 32,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { vpc } from "./vpc";
|
|
||||||
import { postgres } from "./postgres";
|
|
||||||
import { steamEncryptionKey } from "./secret";
|
|
||||||
|
|
||||||
export const LibraryQueue = new sst.aws.Queue("LibraryQueue", {
|
|
||||||
fifo: true,
|
|
||||||
visibilityTimeout: "10 minutes",
|
|
||||||
});
|
|
||||||
|
|
||||||
LibraryQueue.subscribe({
|
|
||||||
vpc,
|
|
||||||
timeout: "10 minutes",
|
|
||||||
memory: "3002 MB",
|
|
||||||
handler: "packages/functions/src/queues/library.handler",
|
|
||||||
link: [
|
|
||||||
postgres,
|
|
||||||
steamEncryptionKey
|
|
||||||
],
|
|
||||||
});
|
|
||||||
@@ -1 +1,5 @@
|
|||||||
export const storage = new sst.aws.Bucket("Storage");
|
export const storage = new sst.aws.Bucket("Storage", {
|
||||||
|
access: "cloudfront"
|
||||||
|
});
|
||||||
|
|
||||||
|
export const zeroStorage = new sst.aws.Bucket("ZeroStorage");
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
// This is the website part where people play and connect
|
// This is the website part where people play and connect
|
||||||
import { api } from "./api";
|
import { api } from "./api";
|
||||||
|
import { cdn } from "./cdn";
|
||||||
import { auth } from "./auth";
|
import { auth } from "./auth";
|
||||||
import { zero } from "./zero";
|
import { zero } from "./zero";
|
||||||
import { domain } from "./dns";
|
import { domain } from "./dns";
|
||||||
@@ -16,6 +17,7 @@ new sst.aws.StaticSite("Web", {
|
|||||||
},
|
},
|
||||||
environment: {
|
environment: {
|
||||||
VITE_API_URL: api.url,
|
VITE_API_URL: api.url,
|
||||||
|
VITE_CDN_URL: cdn.url,
|
||||||
VITE_STAGE: $app.stage,
|
VITE_STAGE: $app.stage,
|
||||||
VITE_AUTH_URL: auth.url,
|
VITE_AUTH_URL: auth.url,
|
||||||
VITE_ZERO_URL: zero.url,
|
VITE_ZERO_URL: zero.url,
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { vpc } from "./vpc";
|
|
||||||
import { auth } from "./auth";
|
import { auth } from "./auth";
|
||||||
import { domain } from "./dns";
|
import { domain } from "./dns";
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
import { cluster } from "./cluster";
|
import { cluster } from "./cluster";
|
||||||
import { storage } from "./storage";
|
|
||||||
import { postgres } from "./postgres";
|
import { postgres } from "./postgres";
|
||||||
|
import { zeroStorage } from "./storage";
|
||||||
|
|
||||||
// const connectionString = $interpolate`postgresql://${postgres.username}:${postgres.password}@${postgres.host}/${postgres.database}`
|
|
||||||
const connectionString = $interpolate`postgresql://${postgres.username}:${postgres.password}@${postgres.host}:${postgres.port}/${postgres.database}`;
|
const connectionString = $interpolate`postgresql://${postgres.username}:${postgres.password}@${postgres.host}:${postgres.port}/${postgres.database}`;
|
||||||
|
|
||||||
const tag = $dev
|
const tag = $dev
|
||||||
@@ -28,12 +26,13 @@ const zeroEnv = {
|
|||||||
ZERO_LITESTREAM_RESTORE_PARALLELISM: "64",
|
ZERO_LITESTREAM_RESTORE_PARALLELISM: "64",
|
||||||
ZERO_APP_ID: $app.stage,
|
ZERO_APP_ID: $app.stage,
|
||||||
ZERO_AUTH_JWKS_URL: $interpolate`${auth.url}/.well-known/jwks.json`,
|
ZERO_AUTH_JWKS_URL: $interpolate`${auth.url}/.well-known/jwks.json`,
|
||||||
|
ZERO_INITIAL_SYNC_ROW_BATCH_SIZE: "30000",
|
||||||
NODE_OPTIONS: "--max-old-space-size=8192",
|
NODE_OPTIONS: "--max-old-space-size=8192",
|
||||||
...($dev
|
...($dev
|
||||||
? {
|
? {
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
ZERO_LITESTREAM_BACKUP_URL: $interpolate`s3://${storage.name}/zero/0`,
|
ZERO_LITESTREAM_BACKUP_URL: $interpolate`s3://${zeroStorage.name}/zero/0`,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -42,15 +41,12 @@ const replicationManager = !$dev
|
|||||||
? new sst.aws.Service(`ZeroReplication`, {
|
? new sst.aws.Service(`ZeroReplication`, {
|
||||||
cluster,
|
cluster,
|
||||||
wait: true,
|
wait: true,
|
||||||
...($app.stage === "production"
|
cpu: "0.5 vCPU",
|
||||||
? {
|
memory: "1 GB",
|
||||||
cpu: "2 vCPU",
|
capacity: "spot",
|
||||||
memory: "4 GB",
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
architecture: "arm64",
|
architecture: "arm64",
|
||||||
image: zeroEnv.ZERO_IMAGE_URL,
|
image: zeroEnv.ZERO_IMAGE_URL,
|
||||||
link: [storage, postgres],
|
link: [zeroStorage, postgres],
|
||||||
health: {
|
health: {
|
||||||
command: ["CMD-SHELL", "curl -f http://localhost:4849/ || exit 1"],
|
command: ["CMD-SHELL", "curl -f http://localhost:4849/ || exit 1"],
|
||||||
interval: "5 seconds",
|
interval: "5 seconds",
|
||||||
@@ -127,17 +123,11 @@ const replicationManager = !$dev
|
|||||||
export const zero = new sst.aws.Service("Zero", {
|
export const zero = new sst.aws.Service("Zero", {
|
||||||
cluster,
|
cluster,
|
||||||
image: zeroEnv.ZERO_IMAGE_URL,
|
image: zeroEnv.ZERO_IMAGE_URL,
|
||||||
link: [storage, postgres],
|
link: [zeroStorage, postgres],
|
||||||
architecture: "arm64",
|
architecture: "arm64",
|
||||||
...($app.stage === "production"
|
cpu: "0.5 vCPU",
|
||||||
? {
|
memory: "1 GB",
|
||||||
cpu: "2 vCPU",
|
capacity: "spot",
|
||||||
memory: "4 GB",
|
|
||||||
capacity: "spot"
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
capacity: "spot"
|
|
||||||
}),
|
|
||||||
environment: {
|
environment: {
|
||||||
...zeroEnv,
|
...zeroEnv,
|
||||||
...($dev
|
...($dev
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/workers-types": "4.20240821.1",
|
"@cloudflare/workers-types": "4.20240821.1",
|
||||||
"@pulumi/pulumi": "^3.134.0",
|
"@pulumi/pulumi": "^3.134.0",
|
||||||
|
"@tsconfig/node22": "^22.0.1",
|
||||||
"@types/aws-lambda": "8.10.147",
|
"@types/aws-lambda": "8.10.147",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"typescript": "^5.4.5"
|
"typescript": "^5.4.5"
|
||||||
@@ -18,7 +19,6 @@
|
|||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@openauthjs/openauth": "0.4.3",
|
"@openauthjs/openauth": "0.4.3",
|
||||||
"@rocicorp/zero": "0.20.2025050901",
|
|
||||||
"steam-session": "1.9.3"
|
"steam-session": "1.9.3"
|
||||||
},
|
},
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
@@ -30,7 +30,6 @@
|
|||||||
"core-js-pure",
|
"core-js-pure",
|
||||||
"esbuild",
|
"esbuild",
|
||||||
"protobufjs",
|
"protobufjs",
|
||||||
"@rocicorp/zero-sqlite3",
|
|
||||||
"workerd"
|
"workerd"
|
||||||
],
|
],
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
|
|||||||
23
packages/core/migrations/0020_vengeful_wallop.sql
Normal file
23
packages/core/migrations/0020_vengeful_wallop.sql
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
ALTER TABLE "steam_account_credentials" DISABLE ROW LEVEL SECURITY;--> statement-breakpoint
|
||||||
|
DROP TABLE "steam_account_credentials" CASCADE;--> statement-breakpoint
|
||||||
|
ALTER TABLE "game_libraries" RENAME COLUMN "owner_id" TO "owner_steam_id";--> statement-breakpoint
|
||||||
|
ALTER TABLE "teams" RENAME COLUMN "owner_id" TO "owner_steam_id";--> statement-breakpoint
|
||||||
|
ALTER TABLE "steam_accounts" DROP CONSTRAINT "idx_steam_username";--> statement-breakpoint
|
||||||
|
ALTER TABLE "game_libraries" DROP CONSTRAINT "game_libraries_owner_id_steam_accounts_id_fk";
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "teams" DROP CONSTRAINT "teams_owner_id_users_id_fk";
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "teams" DROP CONSTRAINT "teams_slug_steam_accounts_username_fk";
|
||||||
|
--> statement-breakpoint
|
||||||
|
DROP INDEX "idx_team_slug";--> statement-breakpoint
|
||||||
|
DROP INDEX "idx_game_libraries_owner_id";--> statement-breakpoint
|
||||||
|
ALTER TABLE "game_libraries" DROP CONSTRAINT "game_libraries_base_game_id_owner_id_pk";--> statement-breakpoint
|
||||||
|
ALTER TABLE "game_libraries" ALTER COLUMN "last_played" DROP NOT NULL;--> statement-breakpoint
|
||||||
|
ALTER TABLE "game_libraries" ADD CONSTRAINT "game_libraries_base_game_id_owner_steam_id_pk" PRIMARY KEY("base_game_id","owner_steam_id");--> statement-breakpoint
|
||||||
|
ALTER TABLE "game_libraries" ADD CONSTRAINT "game_libraries_owner_steam_id_steam_accounts_id_fk" FOREIGN KEY ("owner_steam_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "teams" ADD CONSTRAINT "teams_owner_steam_id_steam_accounts_id_fk" FOREIGN KEY ("owner_steam_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
CREATE INDEX "idx_game_libraries_owner_id" ON "game_libraries" USING btree ("owner_steam_id");--> statement-breakpoint
|
||||||
|
ALTER TABLE "game_libraries" DROP COLUMN "time_acquired";--> statement-breakpoint
|
||||||
|
ALTER TABLE "game_libraries" DROP COLUMN "is_family_shared";--> statement-breakpoint
|
||||||
|
ALTER TABLE "steam_accounts" DROP COLUMN "username";--> statement-breakpoint
|
||||||
|
ALTER TABLE "teams" DROP COLUMN "slug";
|
||||||
2
packages/core/migrations/0021_real_skreet.sql
Normal file
2
packages/core/migrations/0021_real_skreet.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TYPE "public"."category_type" ADD VALUE 'category';--> statement-breakpoint
|
||||||
|
ALTER TYPE "public"."category_type" ADD VALUE 'franchise';
|
||||||
6
packages/core/migrations/0022_clean_living_lightning.sql
Normal file
6
packages/core/migrations/0022_clean_living_lightning.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
ALTER TABLE "public"."categories" ALTER COLUMN "type" SET DATA TYPE text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "public"."games" ALTER COLUMN "type" SET DATA TYPE text;--> statement-breakpoint
|
||||||
|
DROP TYPE "public"."category_type";--> statement-breakpoint
|
||||||
|
CREATE TYPE "public"."category_type" AS ENUM('tag', 'genre', 'publisher', 'developer', 'categorie', 'franchise');--> statement-breakpoint
|
||||||
|
ALTER TABLE "public"."categories" ALTER COLUMN "type" SET DATA TYPE "public"."category_type" USING "type"::"public"."category_type";--> statement-breakpoint
|
||||||
|
ALTER TABLE "public"."games" ALTER COLUMN "type" SET DATA TYPE "public"."category_type" USING "type"::"public"."category_type";
|
||||||
2
packages/core/migrations/0023_flawless_steel_serpent.sql
Normal file
2
packages/core/migrations/0023_flawless_steel_serpent.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE "base_games" ALTER COLUMN "description" DROP NOT NULL;--> statement-breakpoint
|
||||||
|
ALTER TABLE "base_games" ADD COLUMN "links" text[];
|
||||||
1
packages/core/migrations/0024_damp_cerise.sql
Normal file
1
packages/core/migrations/0024_damp_cerise.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "base_games" ALTER COLUMN "links" SET DATA TYPE json;
|
||||||
3
packages/core/migrations/0025_bitter_jack_flag.sql
Normal file
3
packages/core/migrations/0025_bitter_jack_flag.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
DROP TABLE "members" CASCADE;--> statement-breakpoint
|
||||||
|
DROP TABLE "teams" CASCADE;--> statement-breakpoint
|
||||||
|
DROP TYPE "public"."member_role";
|
||||||
1158
packages/core/migrations/meta/0020_snapshot.json
Normal file
1158
packages/core/migrations/meta/0020_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
1160
packages/core/migrations/meta/0021_snapshot.json
Normal file
1160
packages/core/migrations/meta/0021_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
1160
packages/core/migrations/meta/0022_snapshot.json
Normal file
1160
packages/core/migrations/meta/0022_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
1166
packages/core/migrations/meta/0023_snapshot.json
Normal file
1166
packages/core/migrations/meta/0023_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
1166
packages/core/migrations/meta/0024_snapshot.json
Normal file
1166
packages/core/migrations/meta/0024_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
930
packages/core/migrations/meta/0025_snapshot.json
Normal file
930
packages/core/migrations/meta/0025_snapshot.json
Normal file
@@ -0,0 +1,930 @@
|
|||||||
|
{
|
||||||
|
"id": "735d315b-40e1-46c1-814d-0fd3619b65de",
|
||||||
|
"prevId": "d35aa09b-5739-46a5-86f3-3050913dc2f7",
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"tables": {
|
||||||
|
"public.base_games": {
|
||||||
|
"name": "base_games",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"links": {
|
||||||
|
"name": "links",
|
||||||
|
"type": "json",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"slug": {
|
||||||
|
"name": "slug",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"release_date": {
|
||||||
|
"name": "release_date",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"name": "size",
|
||||||
|
"type": "json",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"primary_genre": {
|
||||||
|
"name": "primary_genre",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"controller_support": {
|
||||||
|
"name": "controller_support",
|
||||||
|
"type": "controller_support",
|
||||||
|
"typeSchema": "public",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"compatibility": {
|
||||||
|
"name": "compatibility",
|
||||||
|
"type": "compatibility",
|
||||||
|
"typeSchema": "public",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'unknown'"
|
||||||
|
},
|
||||||
|
"score": {
|
||||||
|
"name": "score",
|
||||||
|
"type": "numeric(2, 1)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"idx_base_games_slug": {
|
||||||
|
"name": "idx_base_games_slug",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"slug"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.categories": {
|
||||||
|
"name": "categories",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"slug": {
|
||||||
|
"name": "slug",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "category_type",
|
||||||
|
"typeSchema": "public",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"idx_categories_type": {
|
||||||
|
"name": "idx_categories_type",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"expression": "type",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isUnique": false,
|
||||||
|
"concurrently": false,
|
||||||
|
"method": "btree",
|
||||||
|
"with": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"categories_slug_type_pk": {
|
||||||
|
"name": "categories_slug_type_pk",
|
||||||
|
"columns": [
|
||||||
|
"slug",
|
||||||
|
"type"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.friends_list": {
|
||||||
|
"name": "friends_list",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"steam_id": {
|
||||||
|
"name": "steam_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"friend_steam_id": {
|
||||||
|
"name": "friend_steam_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"idx_friends_list_friend_steam_id": {
|
||||||
|
"name": "idx_friends_list_friend_steam_id",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"expression": "friend_steam_id",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isUnique": false,
|
||||||
|
"concurrently": false,
|
||||||
|
"method": "btree",
|
||||||
|
"with": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"friends_list_steam_id_steam_accounts_id_fk": {
|
||||||
|
"name": "friends_list_steam_id_steam_accounts_id_fk",
|
||||||
|
"tableFrom": "friends_list",
|
||||||
|
"tableTo": "steam_accounts",
|
||||||
|
"columnsFrom": [
|
||||||
|
"steam_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"friends_list_friend_steam_id_steam_accounts_id_fk": {
|
||||||
|
"name": "friends_list_friend_steam_id_steam_accounts_id_fk",
|
||||||
|
"tableFrom": "friends_list",
|
||||||
|
"tableTo": "steam_accounts",
|
||||||
|
"columnsFrom": [
|
||||||
|
"friend_steam_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"friends_list_steam_id_friend_steam_id_pk": {
|
||||||
|
"name": "friends_list_steam_id_friend_steam_id_pk",
|
||||||
|
"columns": [
|
||||||
|
"steam_id",
|
||||||
|
"friend_steam_id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.games": {
|
||||||
|
"name": "games",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"base_game_id": {
|
||||||
|
"name": "base_game_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"category_slug": {
|
||||||
|
"name": "category_slug",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "category_type",
|
||||||
|
"typeSchema": "public",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"idx_games_category_slug": {
|
||||||
|
"name": "idx_games_category_slug",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"expression": "category_slug",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isUnique": false,
|
||||||
|
"concurrently": false,
|
||||||
|
"method": "btree",
|
||||||
|
"with": {}
|
||||||
|
},
|
||||||
|
"idx_games_category_type": {
|
||||||
|
"name": "idx_games_category_type",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"expression": "type",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isUnique": false,
|
||||||
|
"concurrently": false,
|
||||||
|
"method": "btree",
|
||||||
|
"with": {}
|
||||||
|
},
|
||||||
|
"idx_games_category_slug_type": {
|
||||||
|
"name": "idx_games_category_slug_type",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"expression": "category_slug",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expression": "type",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isUnique": false,
|
||||||
|
"concurrently": false,
|
||||||
|
"method": "btree",
|
||||||
|
"with": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"games_base_game_id_base_games_id_fk": {
|
||||||
|
"name": "games_base_game_id_base_games_id_fk",
|
||||||
|
"tableFrom": "games",
|
||||||
|
"tableTo": "base_games",
|
||||||
|
"columnsFrom": [
|
||||||
|
"base_game_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"games_categories_fkey": {
|
||||||
|
"name": "games_categories_fkey",
|
||||||
|
"tableFrom": "games",
|
||||||
|
"tableTo": "categories",
|
||||||
|
"columnsFrom": [
|
||||||
|
"category_slug",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"slug",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"games_base_game_id_category_slug_type_pk": {
|
||||||
|
"name": "games_base_game_id_category_slug_type_pk",
|
||||||
|
"columns": [
|
||||||
|
"base_game_id",
|
||||||
|
"category_slug",
|
||||||
|
"type"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.images": {
|
||||||
|
"name": "images",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "image_type",
|
||||||
|
"typeSchema": "public",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"image_hash": {
|
||||||
|
"name": "image_hash",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"base_game_id": {
|
||||||
|
"name": "base_game_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"source_url": {
|
||||||
|
"name": "source_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"name": "position",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"file_size": {
|
||||||
|
"name": "file_size",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"dimensions": {
|
||||||
|
"name": "dimensions",
|
||||||
|
"type": "json",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"extracted_color": {
|
||||||
|
"name": "extracted_color",
|
||||||
|
"type": "json",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"idx_images_type": {
|
||||||
|
"name": "idx_images_type",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"expression": "type",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isUnique": false,
|
||||||
|
"concurrently": false,
|
||||||
|
"method": "btree",
|
||||||
|
"with": {}
|
||||||
|
},
|
||||||
|
"idx_images_game_id": {
|
||||||
|
"name": "idx_images_game_id",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"expression": "base_game_id",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isUnique": false,
|
||||||
|
"concurrently": false,
|
||||||
|
"method": "btree",
|
||||||
|
"with": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"images_base_game_id_base_games_id_fk": {
|
||||||
|
"name": "images_base_game_id_base_games_id_fk",
|
||||||
|
"tableFrom": "images",
|
||||||
|
"tableTo": "base_games",
|
||||||
|
"columnsFrom": [
|
||||||
|
"base_game_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"images_image_hash_type_base_game_id_position_pk": {
|
||||||
|
"name": "images_image_hash_type_base_game_id_position_pk",
|
||||||
|
"columns": [
|
||||||
|
"image_hash",
|
||||||
|
"type",
|
||||||
|
"base_game_id",
|
||||||
|
"position"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.game_libraries": {
|
||||||
|
"name": "game_libraries",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"base_game_id": {
|
||||||
|
"name": "base_game_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"owner_steam_id": {
|
||||||
|
"name": "owner_steam_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"last_played": {
|
||||||
|
"name": "last_played",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"total_playtime": {
|
||||||
|
"name": "total_playtime",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"idx_game_libraries_owner_id": {
|
||||||
|
"name": "idx_game_libraries_owner_id",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"expression": "owner_steam_id",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isUnique": false,
|
||||||
|
"concurrently": false,
|
||||||
|
"method": "btree",
|
||||||
|
"with": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"game_libraries_base_game_id_base_games_id_fk": {
|
||||||
|
"name": "game_libraries_base_game_id_base_games_id_fk",
|
||||||
|
"tableFrom": "game_libraries",
|
||||||
|
"tableTo": "base_games",
|
||||||
|
"columnsFrom": [
|
||||||
|
"base_game_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"game_libraries_owner_steam_id_steam_accounts_id_fk": {
|
||||||
|
"name": "game_libraries_owner_steam_id_steam_accounts_id_fk",
|
||||||
|
"tableFrom": "game_libraries",
|
||||||
|
"tableTo": "steam_accounts",
|
||||||
|
"columnsFrom": [
|
||||||
|
"owner_steam_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"game_libraries_base_game_id_owner_steam_id_pk": {
|
||||||
|
"name": "game_libraries_base_game_id_owner_steam_id_pk",
|
||||||
|
"columns": [
|
||||||
|
"base_game_id",
|
||||||
|
"owner_steam_id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.steam_accounts": {
|
||||||
|
"name": "steam_accounts",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "char(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "steam_status",
|
||||||
|
"typeSchema": "public",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"last_synced_at": {
|
||||||
|
"name": "last_synced_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"real_name": {
|
||||||
|
"name": "real_name",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"member_since": {
|
||||||
|
"name": "member_since",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"profile_url": {
|
||||||
|
"name": "profile_url",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"avatar_hash": {
|
||||||
|
"name": "avatar_hash",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"limitations": {
|
||||||
|
"name": "limitations",
|
||||||
|
"type": "json",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"steam_accounts_user_id_users_id_fk": {
|
||||||
|
"name": "steam_accounts_user_id_users_id_fk",
|
||||||
|
"tableFrom": "steam_accounts",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.users": {
|
||||||
|
"name": "users",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "char(30)",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"avatar_url": {
|
||||||
|
"name": "avatar_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"last_login": {
|
||||||
|
"name": "last_login",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"polar_customer_id": {
|
||||||
|
"name": "polar_customer_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"idx_user_email": {
|
||||||
|
"name": "idx_user_email",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"email"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {
|
||||||
|
"public.compatibility": {
|
||||||
|
"name": "compatibility",
|
||||||
|
"schema": "public",
|
||||||
|
"values": [
|
||||||
|
"high",
|
||||||
|
"mid",
|
||||||
|
"low",
|
||||||
|
"unknown"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"public.controller_support": {
|
||||||
|
"name": "controller_support",
|
||||||
|
"schema": "public",
|
||||||
|
"values": [
|
||||||
|
"full",
|
||||||
|
"partial",
|
||||||
|
"unknown"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"public.category_type": {
|
||||||
|
"name": "category_type",
|
||||||
|
"schema": "public",
|
||||||
|
"values": [
|
||||||
|
"tag",
|
||||||
|
"genre",
|
||||||
|
"publisher",
|
||||||
|
"developer",
|
||||||
|
"categorie",
|
||||||
|
"franchise"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"public.image_type": {
|
||||||
|
"name": "image_type",
|
||||||
|
"schema": "public",
|
||||||
|
"values": [
|
||||||
|
"heroArt",
|
||||||
|
"icon",
|
||||||
|
"logo",
|
||||||
|
"banner",
|
||||||
|
"poster",
|
||||||
|
"boxArt",
|
||||||
|
"screenshot",
|
||||||
|
"backdrop"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"public.steam_status": {
|
||||||
|
"name": "steam_status",
|
||||||
|
"schema": "public",
|
||||||
|
"values": [
|
||||||
|
"online",
|
||||||
|
"offline",
|
||||||
|
"dnd",
|
||||||
|
"playing"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemas": {},
|
||||||
|
"sequences": {},
|
||||||
|
"roles": {},
|
||||||
|
"policies": {},
|
||||||
|
"views": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -141,6 +141,48 @@
|
|||||||
"when": 1747202158003,
|
"when": 1747202158003,
|
||||||
"tag": "0019_charming_namorita",
|
"tag": "0019_charming_namorita",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 20,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1747795508868,
|
||||||
|
"tag": "0020_vengeful_wallop",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 21,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1747975397543,
|
||||||
|
"tag": "0021_real_skreet",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 22,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1748099972605,
|
||||||
|
"tag": "0022_clean_living_lightning",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 23,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1748411845939,
|
||||||
|
"tag": "0023_flawless_steel_serpent",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 24,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1748414049463,
|
||||||
|
"tag": "0024_damp_cerise",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 25,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1748845818197,
|
||||||
|
"tag": "0025_bitter_jack_flag",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
"@tsconfig/node20": "^20.1.4",
|
"@tsconfig/node20": "^20.1.4",
|
||||||
"@types/pngjs": "^6.0.5",
|
"@types/pngjs": "^6.0.5",
|
||||||
"@types/sanitize-html": "^2.16.0",
|
"@types/sanitize-html": "^2.16.0",
|
||||||
|
"@types/xml2js": "^0.4.14",
|
||||||
"aws-iot-device-sdk-v2": "^1.21.1",
|
"aws-iot-device-sdk-v2": "^1.21.1",
|
||||||
"aws4fetch": "^1.0.20",
|
"aws4fetch": "^1.0.20",
|
||||||
"mqtt": "^5.10.3",
|
"mqtt": "^5.10.3",
|
||||||
@@ -42,6 +43,7 @@
|
|||||||
"postgres": "^3.4.5",
|
"postgres": "^3.4.5",
|
||||||
"sanitize-html": "^2.16.0",
|
"sanitize-html": "^2.16.0",
|
||||||
"sharp": "^0.34.1",
|
"sharp": "^0.34.1",
|
||||||
"steam-session": "*"
|
"steam-session": "*",
|
||||||
|
"xml2js": "^0.6.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { User } from "../user";
|
import { User } from "../user";
|
||||||
import { Team } from "../team";
|
import { Steam } from "../steam";
|
||||||
import { Actor } from "../actor";
|
import { Actor } from "../actor";
|
||||||
import { Examples } from "../examples";
|
import { Examples } from "../examples";
|
||||||
import { ErrorCodes, VisibleError } from "../error";
|
import { ErrorCodes, VisibleError } from "../error";
|
||||||
@@ -9,26 +9,26 @@ export namespace Account {
|
|||||||
export const Info =
|
export const Info =
|
||||||
User.Info
|
User.Info
|
||||||
.extend({
|
.extend({
|
||||||
teams: Team.Info
|
profiles: Steam.Info
|
||||||
.array()
|
.array()
|
||||||
.openapi({
|
.openapi({
|
||||||
description: "The teams that this user is part of",
|
description: "The Steam accounts this user owns",
|
||||||
example: [Examples.Team]
|
example: [Examples.SteamAccount]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.openapi({
|
.openapi({
|
||||||
ref: "Account",
|
ref: "Account",
|
||||||
description: "Represents an account's information stored on Nestri",
|
description: "Represents an account's information stored on Nestri",
|
||||||
example: { ...Examples.User, teams: [Examples.Team] },
|
example: { ...Examples.User, profiles: [Examples.SteamAccount] },
|
||||||
});
|
});
|
||||||
|
|
||||||
export type Info = z.infer<typeof Info>;
|
export type Info = z.infer<typeof Info>;
|
||||||
|
|
||||||
export const list = async (): Promise<Info> => {
|
export const list = async (): Promise<Info> => {
|
||||||
const [userResult, teamsResult] =
|
const [userResult, steamResult] =
|
||||||
await Promise.allSettled([
|
await Promise.allSettled([
|
||||||
User.fromID(Actor.userID()),
|
User.fromID(Actor.userID()),
|
||||||
Team.list()
|
Steam.list()
|
||||||
])
|
])
|
||||||
|
|
||||||
if (userResult.status === "rejected" || !userResult.value)
|
if (userResult.status === "rejected" || !userResult.value)
|
||||||
@@ -40,7 +40,7 @@ export namespace Account {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...userResult.value,
|
...userResult.value,
|
||||||
teams: teamsResult.status === "rejected" ? [] : teamsResult.value
|
profiles: steamResult.status === "rejected" ? [] : steamResult.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ export namespace Actor {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface System {
|
export interface Steam {
|
||||||
type: "system";
|
type: "steam";
|
||||||
properties: {
|
properties: {
|
||||||
teamID: string;
|
steamID: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +32,6 @@ export namespace Actor {
|
|||||||
properties: {
|
properties: {
|
||||||
userID: string;
|
userID: string;
|
||||||
steamID: string;
|
steamID: string;
|
||||||
teamID: string;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +40,7 @@ export namespace Actor {
|
|||||||
properties: {};
|
properties: {};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Info = User | Public | Token | System | Machine;
|
export type Info = User | Public | Token | Machine | Steam;
|
||||||
|
|
||||||
export const Context = createContext<Info>();
|
export const Context = createContext<Info>();
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,12 @@ export const Size =
|
|||||||
z.object({
|
z.object({
|
||||||
downloadSize: z.number().positive().int(),
|
downloadSize: z.number().positive().int(),
|
||||||
sizeOnDisk: z.number().positive().int()
|
sizeOnDisk: z.number().positive().int()
|
||||||
})
|
});
|
||||||
|
|
||||||
export type Size = z.infer<typeof Size>
|
export const Links = z.string().array();
|
||||||
|
|
||||||
|
export type Size = z.infer<typeof Size>;
|
||||||
|
export type Links = z.infer<typeof Links>;
|
||||||
|
|
||||||
export const baseGamesTable = pgTable(
|
export const baseGamesTable = pgTable(
|
||||||
"base_games",
|
"base_games",
|
||||||
@@ -20,12 +23,13 @@ export const baseGamesTable = pgTable(
|
|||||||
id: varchar("id", { length: 255 })
|
id: varchar("id", { length: 255 })
|
||||||
.primaryKey()
|
.primaryKey()
|
||||||
.notNull(),
|
.notNull(),
|
||||||
|
links: json("links").$type<Links>(),
|
||||||
slug: varchar("slug", { length: 255 })
|
slug: varchar("slug", { length: 255 })
|
||||||
.notNull(),
|
.notNull(),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
|
description: text("description"),
|
||||||
releaseDate: utc("release_date").notNull(),
|
releaseDate: utc("release_date").notNull(),
|
||||||
size: json("size").$type<Size>().notNull(),
|
size: json("size").$type<Size>().notNull(),
|
||||||
description: text("description").notNull(),
|
|
||||||
primaryGenre: text("primary_genre"),
|
primaryGenre: text("primary_genre"),
|
||||||
controllerSupport: ControllerEnum("controller_support").notNull(),
|
controllerSupport: ControllerEnum("controller_support").notNull(),
|
||||||
compatibility: CompatibilityEnum("compatibility").notNull().default("unknown"),
|
compatibility: CompatibilityEnum("compatibility").notNull().default("unknown"),
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { fn } from "../utils";
|
import { fn } from "../utils";
|
||||||
import { Resource } from "sst";
|
|
||||||
import { bus } from "sst/aws/bus";
|
|
||||||
import { Common } from "../common";
|
import { Common } from "../common";
|
||||||
import { Examples } from "../examples";
|
import { Examples } from "../examples";
|
||||||
import { createEvent } from "../event";
|
import { createEvent } from "../event";
|
||||||
import { eq, isNull, and } from "drizzle-orm";
|
import { eq, isNull, and } from "drizzle-orm";
|
||||||
import { afterTx, createTransaction, useTransaction } from "../drizzle/transaction";
|
import { ImageTypeEnum } from "../images/images.sql";
|
||||||
import { CompatibilityEnum, baseGamesTable, Size, ControllerEnum } from "./base-game.sql";
|
import { createTransaction, useTransaction } from "../drizzle/transaction";
|
||||||
|
import { CompatibilityEnum, baseGamesTable, Size, ControllerEnum, Links } from "./base-game.sql";
|
||||||
|
|
||||||
export namespace BaseGame {
|
export namespace BaseGame {
|
||||||
export const Info = z.object({
|
export const Info = z.object({
|
||||||
@@ -31,7 +30,7 @@ export namespace BaseGame {
|
|||||||
description: "The initial public release date of the game on Steam",
|
description: "The initial public release date of the game on Steam",
|
||||||
example: Examples.BaseGame.releaseDate
|
example: Examples.BaseGame.releaseDate
|
||||||
}),
|
}),
|
||||||
description: z.string().openapi({
|
description: z.string().nullable().openapi({
|
||||||
description: "A comprehensive overview of the game, including its features, storyline, and gameplay elements",
|
description: "A comprehensive overview of the game, including its features, storyline, and gameplay elements",
|
||||||
example: Examples.BaseGame.description
|
example: Examples.BaseGame.description
|
||||||
}),
|
}),
|
||||||
@@ -39,6 +38,12 @@ export namespace BaseGame {
|
|||||||
description: "The aggregate user review score on Steam, represented as a percentage of positive reviews",
|
description: "The aggregate user review score on Steam, represented as a percentage of positive reviews",
|
||||||
example: Examples.BaseGame.score
|
example: Examples.BaseGame.score
|
||||||
}),
|
}),
|
||||||
|
links: Links
|
||||||
|
.nullable()
|
||||||
|
.openapi({
|
||||||
|
description: "The social links of this game",
|
||||||
|
example: Examples.BaseGame.links
|
||||||
|
}),
|
||||||
primaryGenre: z.string().nullable().openapi({
|
primaryGenre: z.string().nullable().openapi({
|
||||||
description: "The main category or genre that best represents the game's content and gameplay style",
|
description: "The main category or genre that best represents the game's content and gameplay style",
|
||||||
example: Examples.BaseGame.primaryGenre
|
example: Examples.BaseGame.primaryGenre
|
||||||
@@ -50,7 +55,7 @@ export namespace BaseGame {
|
|||||||
compatibility: z.enum(CompatibilityEnum.enumValues).openapi({
|
compatibility: z.enum(CompatibilityEnum.enumValues).openapi({
|
||||||
description: "Steam Deck/Proton compatibility rating indicating how well the game runs on Linux systems",
|
description: "Steam Deck/Proton compatibility rating indicating how well the game runs on Linux systems",
|
||||||
example: Examples.BaseGame.compatibility
|
example: Examples.BaseGame.compatibility
|
||||||
})
|
}),
|
||||||
}).openapi({
|
}).openapi({
|
||||||
ref: "BaseGame",
|
ref: "BaseGame",
|
||||||
description: "Detailed information about a game available in the Nestri library, including technical specifications and metadata",
|
description: "Detailed information about a game available in the Nestri library, including technical specifications and metadata",
|
||||||
@@ -61,9 +66,27 @@ export namespace BaseGame {
|
|||||||
|
|
||||||
export const Events = {
|
export const Events = {
|
||||||
New: createEvent(
|
New: createEvent(
|
||||||
"new_game.added",
|
"new_image.save",
|
||||||
z.object({
|
z.object({
|
||||||
appID: Info.shape.id,
|
appID: Info.shape.id,
|
||||||
|
url: z.string().url(),
|
||||||
|
type: z.enum(ImageTypeEnum.enumValues)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
NewBoxArt: createEvent(
|
||||||
|
"new_box_art_image.save",
|
||||||
|
z.object({
|
||||||
|
appID: Info.shape.id,
|
||||||
|
logoUrl: z.string().url(),
|
||||||
|
backgroundUrl: z.string().url(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
NewHeroArt: createEvent(
|
||||||
|
"new_hero_art_image.save",
|
||||||
|
z.object({
|
||||||
|
appID: Info.shape.id,
|
||||||
|
backdropUrl: z.string().url(),
|
||||||
|
screenshots: z.string().url().array(),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
@@ -72,6 +95,21 @@ export namespace BaseGame {
|
|||||||
Info,
|
Info,
|
||||||
(input) =>
|
(input) =>
|
||||||
createTransaction(async (tx) => {
|
createTransaction(async (tx) => {
|
||||||
|
const result = await tx
|
||||||
|
.select()
|
||||||
|
.from(baseGamesTable)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(baseGamesTable.id, input.id),
|
||||||
|
isNull(baseGamesTable.timeDeleted)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1)
|
||||||
|
.execute()
|
||||||
|
.then(rows => rows.at(0))
|
||||||
|
|
||||||
|
if (result) return result.id
|
||||||
|
|
||||||
await tx
|
await tx
|
||||||
.insert(baseGamesTable)
|
.insert(baseGamesTable)
|
||||||
.values(input)
|
.values(input)
|
||||||
@@ -82,10 +120,6 @@ export namespace BaseGame {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
await afterTx(async () => {
|
|
||||||
await bus.publish(Resource.Bus, Events.New, { appID: input.id })
|
|
||||||
})
|
|
||||||
|
|
||||||
return input.id
|
return input.id
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -116,6 +150,7 @@ export namespace BaseGame {
|
|||||||
name: input.name,
|
name: input.name,
|
||||||
slug: input.slug,
|
slug: input.slug,
|
||||||
size: input.size,
|
size: input.size,
|
||||||
|
links: input.links,
|
||||||
score: input.score,
|
score: input.score,
|
||||||
description: input.description,
|
description: input.description,
|
||||||
releaseDate: input.releaseDate,
|
releaseDate: input.releaseDate,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { timestamps } from "../drizzle/types";
|
import { timestamps } from "../drizzle/types";
|
||||||
import { index, pgEnum, pgTable, primaryKey, text, varchar } from "drizzle-orm/pg-core";
|
import { index, pgEnum, pgTable, primaryKey, text, varchar } from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
export const CategoryTypeEnum = pgEnum("category_type", ["tag", "genre", "publisher", "developer"])
|
// Intentional grammatical error on category
|
||||||
|
export const CategoryTypeEnum = pgEnum("category_type", ["tag", "genre", "publisher", "developer", "categorie", "franchise"])
|
||||||
|
|
||||||
export const categoriesTable = pgTable(
|
export const categoriesTable = pgTable(
|
||||||
"categories",
|
"categories",
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { fn } from "../utils";
|
import { fn } from "../utils";
|
||||||
import { Examples } from "../examples";
|
import { Examples } from "../examples";
|
||||||
|
import { eq, isNull, and } from "drizzle-orm";
|
||||||
import { createSelectSchema } from "drizzle-zod";
|
import { createSelectSchema } from "drizzle-zod";
|
||||||
import { categoriesTable } from "./categories.sql";
|
import { categoriesTable } from "./categories.sql";
|
||||||
import { createTransaction, useTransaction } from "../drizzle/transaction";
|
import { createTransaction, useTransaction } from "../drizzle/transaction";
|
||||||
import { eq, isNull, and } from "drizzle-orm";
|
|
||||||
|
|
||||||
export namespace Categories {
|
export namespace Categories {
|
||||||
|
|
||||||
@@ -36,7 +36,16 @@ export namespace Categories {
|
|||||||
genres: Category.array().openapi({
|
genres: Category.array().openapi({
|
||||||
description: "Primary classification categories that define the game's style and type of gameplay",
|
description: "Primary classification categories that define the game's style and type of gameplay",
|
||||||
example: Examples.Categories.genres
|
example: Examples.Categories.genres
|
||||||
})
|
}),
|
||||||
|
categories: Category.array().openapi({
|
||||||
|
description: "Primary classification categories that define the game's categorisation on Steam",
|
||||||
|
example: Examples.Categories.genres
|
||||||
|
}),
|
||||||
|
franchises: Category.array().openapi({
|
||||||
|
description: "The franchise this game belongs belongs to on Steam",
|
||||||
|
example: Examples.Categories.genres
|
||||||
|
}),
|
||||||
|
|
||||||
}).openapi({
|
}).openapi({
|
||||||
ref: "Categories",
|
ref: "Categories",
|
||||||
description: "A comprehensive categorization system for games, including publishing details, development credits, and content classification",
|
description: "A comprehensive categorization system for games, including publishing details, development credits, and content classification",
|
||||||
@@ -111,7 +120,9 @@ export namespace Categories {
|
|||||||
tags: [],
|
tags: [],
|
||||||
genres: [],
|
genres: [],
|
||||||
publishers: [],
|
publishers: [],
|
||||||
developers: []
|
developers: [],
|
||||||
|
categories: [],
|
||||||
|
franchises: []
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,159 +1,172 @@
|
|||||||
import type {
|
import type {
|
||||||
|
Shot,
|
||||||
AppInfo,
|
AppInfo,
|
||||||
GameTagsResponse,
|
|
||||||
SteamApiResponse,
|
|
||||||
GameDetailsResponse,
|
|
||||||
SteamAppDataResponse,
|
|
||||||
ImageInfo,
|
ImageInfo,
|
||||||
ImageType,
|
ImageType,
|
||||||
Shot
|
SteamAccount,
|
||||||
|
GameTagsResponse,
|
||||||
|
GameDetailsResponse,
|
||||||
|
SteamAppDataResponse,
|
||||||
|
SteamOwnedGamesResponse,
|
||||||
|
SteamPlayerBansResponse,
|
||||||
|
SteamFriendsListResponse,
|
||||||
|
SteamPlayerSummaryResponse,
|
||||||
|
SteamStoreResponse,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import pLimit from 'p-limit';
|
|
||||||
import SteamID from "steamid";
|
|
||||||
import { fn } from "../utils";
|
import { fn } from "../utils";
|
||||||
|
import { Resource } from "sst";
|
||||||
|
import { Steam } from "./steam";
|
||||||
import { Utils } from "./utils";
|
import { Utils } from "./utils";
|
||||||
import SteamCommunity from "steamcommunity";
|
import { ImageTypeEnum } from "../images/images.sql";
|
||||||
import { Credentials } from "../credentials";
|
|
||||||
import type CSteamUser from "steamcommunity/classes/CSteamUser";
|
|
||||||
|
|
||||||
const requestLimit = pLimit(10); // max concurrent requests
|
|
||||||
|
|
||||||
export namespace Client {
|
export namespace Client {
|
||||||
export const getUserLibrary = fn(
|
export const getUserLibrary = fn(
|
||||||
Credentials.Info.shape.accessToken,
|
z.string(),
|
||||||
async (accessToken) =>
|
async (steamID) =>
|
||||||
await Utils.fetchApi<SteamApiResponse>(`https://api.steampowered.com/IFamilyGroupsService/GetSharedLibraryApps/v1/?access_token=${accessToken}&family_groupid=0&include_excluded=true&include_free=true&include_non_games=false&include_own=true`)
|
await Utils.fetchApi<SteamOwnedGamesResponse>(`https://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=${Resource.SteamApiKey.value}&steamid=${steamID}&include_appinfo=1&format=json&include_played_free_games=1&skip_unvetted_apps=0`)
|
||||||
)
|
)
|
||||||
|
|
||||||
export const getFriendsList = fn(
|
export const getFriendsList = fn(
|
||||||
Credentials.Info.shape.cookies,
|
z.string(),
|
||||||
async (cookies): Promise<CSteamUser[]> => {
|
async (steamID) =>
|
||||||
const community = new SteamCommunity();
|
await Utils.fetchApi<SteamFriendsListResponse>(`https://api.steampowered.com/ISteamUser/GetFriendList/v0001/?key=${Resource.SteamApiKey.value}&steamid=${steamID}&relationship=friend`)
|
||||||
community.setCookies(cookies);
|
|
||||||
|
|
||||||
const allFriends = await new Promise<Record<string, any>>((resolve, reject) => {
|
|
||||||
community.getFriendsList((err, friends) => {
|
|
||||||
if (err) {
|
|
||||||
return reject(new Error(`Could not get friends list: ${err.message}`));
|
|
||||||
}
|
|
||||||
resolve(friends);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const friendIds = Object.keys(allFriends);
|
|
||||||
|
|
||||||
const userPromises: Promise<CSteamUser>[] = friendIds.map(id =>
|
|
||||||
requestLimit(() => new Promise<CSteamUser>((resolve, reject) => {
|
|
||||||
const sid = new SteamID(id);
|
|
||||||
community.getSteamUser(sid, (err, user) => {
|
|
||||||
if (err) {
|
|
||||||
return reject(new Error(`Could not get steam user info for ${id}: ${err.message}`));
|
|
||||||
}
|
|
||||||
resolve(user);
|
|
||||||
});
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
const settled = await Promise.allSettled(userPromises)
|
|
||||||
|
|
||||||
settled
|
|
||||||
.filter(r => r.status === "rejected")
|
|
||||||
.forEach(r => console.warn("[getFriendsList] failed:", (r as PromiseRejectedResult).reason));
|
|
||||||
|
|
||||||
return settled.filter(s => s.status === "fulfilled").map(r => (r as PromiseFulfilledResult<CSteamUser>).value);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getUserInfo = fn(
|
export const getUserInfo = fn(
|
||||||
Credentials.Info.pick({ cookies: true, steamID: true }),
|
z.string().array(),
|
||||||
async (input) =>
|
async (steamIDs) => {
|
||||||
new Promise((resolve, reject) => {
|
const [userInfo, banInfo, profileInfo] = await Promise.all([
|
||||||
const community = new SteamCommunity()
|
Utils.fetchApi<SteamPlayerSummaryResponse>(`https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=${Resource.SteamApiKey.value}&steamids=${steamIDs.join(",")}`),
|
||||||
community.setCookies(input.cookies);
|
Utils.fetchApi<SteamPlayerBansResponse>(`https://api.steampowered.com/ISteamUser/GetPlayerBans/v1/?key=${Resource.SteamApiKey.value}&steamids=${steamIDs.join(",")}`),
|
||||||
const steamID = new SteamID(input.steamID);
|
Utils.fetchProfilesInfo(steamIDs)
|
||||||
community.getSteamUser(steamID, async (err, user) => {
|
])
|
||||||
if (err) {
|
|
||||||
reject(`Could not get steam user info: ${err.message}`)
|
// Create a map of bans by steamID for fast lookup
|
||||||
|
const bansBySteamID = new Map(
|
||||||
|
banInfo.players.map((b) => [b.SteamId, b])
|
||||||
|
);
|
||||||
|
|
||||||
|
// Map userInfo.players to your desired output using Promise.allSettled
|
||||||
|
// to prevent one error from closing down the whole pipeline
|
||||||
|
const steamAccounts = await Promise.allSettled(
|
||||||
|
userInfo.response.players.map(async (player) => {
|
||||||
|
const ban = bansBySteamID.get(player.steamid);
|
||||||
|
const info = profileInfo.get(player.steamid);
|
||||||
|
|
||||||
|
if (!info) {
|
||||||
|
throw new Error(`[userInfo] profile info missing for ${player.steamid}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('error' in info) {
|
||||||
|
throw new Error(`error handling profile info for: ${player.steamid}:${info.error}`)
|
||||||
} else {
|
} else {
|
||||||
resolve(user)
|
return {
|
||||||
|
id: player.steamid,
|
||||||
|
name: player.personaname,
|
||||||
|
realName: player.realname ?? null,
|
||||||
|
steamMemberSince: new Date(player.timecreated * 1000),
|
||||||
|
avatarHash: player.avatarhash,
|
||||||
|
limitations: {
|
||||||
|
isLimited: info.isLimited,
|
||||||
|
privacyState: info.privacyState,
|
||||||
|
isVacBanned: ban?.VACBanned ?? false,
|
||||||
|
tradeBanState: ban?.EconomyBan ?? "none",
|
||||||
|
visibilityState: player.communityvisibilitystate,
|
||||||
|
},
|
||||||
|
lastSyncedAt: new Date(),
|
||||||
|
profileUrl: player.profileurl,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}) as Promise<CSteamUser>
|
);
|
||||||
)
|
|
||||||
|
steamAccounts
|
||||||
|
.filter(result => result.status === 'rejected')
|
||||||
|
.forEach(result => console.warn('[userInfo] failed:', (result as PromiseRejectedResult).reason))
|
||||||
|
|
||||||
|
return steamAccounts.filter(result => result.status === "fulfilled").map(result => (result as PromiseFulfilledResult<SteamAccount>).value)
|
||||||
|
})
|
||||||
|
|
||||||
export const getAppInfo = fn(
|
export const getAppInfo = fn(
|
||||||
z.string(),
|
z.string(),
|
||||||
async (appid) => {
|
async (appid) => {
|
||||||
const [infoData, tagsData, details] = await Promise.all([
|
try {
|
||||||
|
const info = await Promise.all([
|
||||||
Utils.fetchApi<SteamAppDataResponse>(`https://api.steamcmd.net/v1/info/${appid}`),
|
Utils.fetchApi<SteamAppDataResponse>(`https://api.steamcmd.net/v1/info/${appid}`),
|
||||||
Utils.fetchApi<GameTagsResponse>("https://store.steampowered.com/actions/ajaxgetstoretags"),
|
Utils.fetchApi<SteamStoreResponse>(`https://api.steampowered.com/IStoreBrowseService/GetItems/v1/?key=${Resource.SteamApiKey.value}&input_json={"ids":[{"appid":"${appid}"}],"context":{"language":"english","country_code":"US","steam_realm":"1"},"data_request":{"include_assets":true,"include_release":true,"include_platforms":true,"include_all_purchase_options":true,"include_screenshots":true,"include_trailers":true,"include_ratings":true,"include_tag_count":"40","include_reviews":true,"include_basic_info":true,"include_supported_languages":true,"include_full_description":true,"include_included_items":true,"include_assets_without_overrides":true,"apply_user_filters":true,"include_links":true}}`),
|
||||||
Utils.fetchApi<GameDetailsResponse>(
|
|
||||||
`https://store.steampowered.com/apphover/${appid}?full=1&review_score_preference=1&pagev6=true&json=1`
|
|
||||||
),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const tags = tagsData.tags;
|
const cmd = info[0].data[appid]
|
||||||
const game = infoData.data[appid];
|
const store = info[1].response.store_items[0]
|
||||||
// Guard against an empty string - When there are no genres, Steam returns an empty string
|
|
||||||
const genres = details.strGenres ? Utils.parseGenres(details.strGenres) : [];
|
|
||||||
|
|
||||||
const controllerTag = game.common.controller_support ?
|
if (!cmd) {
|
||||||
Utils.createTag(`${Utils.capitalise(game.common.controller_support)} Controller Support`) :
|
throw new Error(`App data not found for appid: ${appid}`)
|
||||||
Utils.createTag(`Unknown Controller Support`)
|
}
|
||||||
|
|
||||||
const compatibilityTag = Utils.createTag(`${Utils.capitalise(Utils.compatibilityType(game.common.steam_deck_compatibility?.category))} Compatibility`)
|
if (!store || store.success !== 1) {
|
||||||
|
throw new Error(`Could not get store information or appid: ${appid}`)
|
||||||
|
}
|
||||||
|
|
||||||
const controller = (game.common.controller_support === "partial" || game.common.controller_support === "full") ? game.common.controller_support : "unknown";
|
const tags = store.tagids
|
||||||
const appInfo: AppInfo = {
|
.map(id => Steam.tags[id.toString() as keyof typeof Steam.tags])
|
||||||
genres,
|
.filter((name): name is string => typeof name === 'string')
|
||||||
gameid: game.appid,
|
|
||||||
name: game.common.name.trim(),
|
|
||||||
size: Utils.getPublicDepotSizes(game.depots!),
|
|
||||||
slug: Utils.createSlug(game.common.name.trim()),
|
|
||||||
description: Utils.cleanDescription(details.strDescription),
|
|
||||||
controllerSupport: controller,
|
|
||||||
releaseDate: new Date(Number(game.common.steam_release_date) * 1000),
|
|
||||||
primaryGenre: (!!game?.common.genres && !!details.strGenres) ? Utils.getPrimaryGenre(
|
|
||||||
genres,
|
|
||||||
game.common.genres!,
|
|
||||||
game.common.primary_genre!
|
|
||||||
) : null,
|
|
||||||
developers: game.common.associations ?
|
|
||||||
Array.from(
|
|
||||||
Utils.getAssociationsByTypeWithSlug(
|
|
||||||
game.common.associations!,
|
|
||||||
"developer"
|
|
||||||
)
|
|
||||||
) : [],
|
|
||||||
publishers: game.common.associations ?
|
|
||||||
Array.from(
|
|
||||||
Utils.getAssociationsByTypeWithSlug(
|
|
||||||
game.common.associations!,
|
|
||||||
"publisher"
|
|
||||||
)
|
|
||||||
) : [],
|
|
||||||
compatibility: Utils.compatibilityType(game.common.steam_deck_compatibility?.category),
|
|
||||||
tags: [
|
|
||||||
...(game?.common.store_tags ?
|
|
||||||
Utils.mapGameTags(
|
|
||||||
tags,
|
|
||||||
game.common.store_tags!,
|
|
||||||
) : []),
|
|
||||||
controllerTag,
|
|
||||||
compatibilityTag
|
|
||||||
],
|
|
||||||
score: Utils.getRating(
|
|
||||||
details.ReviewSummary.cRecommendationsPositive,
|
|
||||||
details.ReviewSummary.cRecommendationsNegative
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
return appInfo
|
const publishers = store.basic_info.publishers
|
||||||
|
.map(i => i.name)
|
||||||
|
|
||||||
|
const developers = store.basic_info.developers
|
||||||
|
.map(i => i.name)
|
||||||
|
|
||||||
|
const franchises = store.basic_info.franchises
|
||||||
|
?.map(i => i.name)
|
||||||
|
|
||||||
|
const genres = cmd?.common.genres &&
|
||||||
|
Object.keys(cmd?.common.genres)
|
||||||
|
.map(id => Steam.genres[id.toString() as keyof typeof Steam.genres])
|
||||||
|
.filter((name): name is string => typeof name === 'string')
|
||||||
|
|
||||||
|
const categories = [
|
||||||
|
...(store.categories?.controller_categoryids?.map(i => Steam.categories[i.toString() as keyof typeof Steam.categories]) ?? []),
|
||||||
|
...(store.categories?.supported_player_categoryids?.map(i => Steam.categories[i.toString() as keyof typeof Steam.categories]) ?? [])
|
||||||
|
].filter((name): name is string => typeof name === 'string')
|
||||||
|
|
||||||
|
const assetUrls = Utils.getAssetUrls(cmd?.common.library_assets_full, appid, cmd?.common.header_image.english);
|
||||||
|
|
||||||
|
const screenshots = store.screenshots.all_ages_screenshots?.map(i => `https://shared.cloudflare.steamstatic.com/store_item_assets/${i.filename}`) ?? [];
|
||||||
|
|
||||||
|
const icon = `https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/${appid}/${cmd?.common.icon}.jpg`;
|
||||||
|
|
||||||
|
const data: AppInfo = {
|
||||||
|
id: appid,
|
||||||
|
name: cmd?.common.name.trim(),
|
||||||
|
tags: Utils.createType(tags, "tag"),
|
||||||
|
images: { screenshots, icon, ...assetUrls },
|
||||||
|
size: Utils.getPublicDepotSizes(cmd?.depots!),
|
||||||
|
slug: Utils.createSlug(cmd?.common.name.trim()),
|
||||||
|
publishers: Utils.createType(publishers, "publisher"),
|
||||||
|
developers: Utils.createType(developers, "developer"),
|
||||||
|
categories: Utils.createType(categories, "categorie"),
|
||||||
|
links: store.links ? store.links.map(i => i.url) : null,
|
||||||
|
genres: genres ? Utils.createType(genres, "genre") : [],
|
||||||
|
franchises: franchises ? Utils.createType(franchises, "franchise") : [],
|
||||||
|
description: store.basic_info.short_description ? Utils.cleanDescription(store.basic_info.short_description) : null,
|
||||||
|
controllerSupport: cmd?.common.controller_support ?? "unknown" as any,
|
||||||
|
releaseDate: new Date(Number(cmd?.common.steam_release_date) * 1000),
|
||||||
|
primaryGenre: !!cmd?.common.primary_genre && Steam.genres[cmd?.common.primary_genre as keyof typeof Steam.genres] ? Steam.genres[cmd?.common.primary_genre as keyof typeof Steam.genres] : null,
|
||||||
|
compatibility: store?.platforms.steam_os_compat_category ? Utils.compatibilityType(store?.platforms.steam_os_compat_category.toString() as any).toLowerCase() : "unknown" as any,
|
||||||
|
score: Utils.estimateRatingFromSummary(store.reviews.summary_filtered.review_count, store.reviews.summary_filtered.percent_positive)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`Error handling: ${appid}`)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
export const getImages = fn(
|
export const getImageUrls = fn(
|
||||||
z.string(),
|
z.string(),
|
||||||
async (appid) => {
|
async (appid) => {
|
||||||
const [appData, details] = await Promise.all([
|
const [appData, details] = await Promise.all([
|
||||||
@@ -167,18 +180,49 @@ export namespace Client {
|
|||||||
if (!game) throw new Error('Game info missing');
|
if (!game) throw new Error('Game info missing');
|
||||||
|
|
||||||
// 2. Prepare URLs
|
// 2. Prepare URLs
|
||||||
const screenshotUrls = Utils.getScreenshotUrls(details.rgScreenshots || []);
|
const screenshots = Utils.getScreenshotUrls(details.rgScreenshots || []);
|
||||||
const assetUrls = Utils.getAssetUrls(game.library_assets_full, appid, game.header_image.english);
|
const assetUrls = Utils.getAssetUrls(game.library_assets_full, appid, game.header_image.english);
|
||||||
const iconUrl = `https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/${appid}/${game.icon}.jpg`;
|
const icon = `https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/${appid}/${game.icon}.jpg`;
|
||||||
|
|
||||||
//2.5 Get the backdrop buffer and use it to get the best screenshot
|
return { screenshots, icon, ...assetUrls }
|
||||||
const baselineBuffer = await Utils.fetchBuffer(assetUrls.backdrop);
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// 3. Download screenshot buffers in parallel
|
export const getImageInfo = fn(
|
||||||
|
z.object({
|
||||||
|
type: z.enum(ImageTypeEnum.enumValues),
|
||||||
|
url: z.string()
|
||||||
|
}),
|
||||||
|
async (input) =>
|
||||||
|
Utils.fetchBuffer(input.url)
|
||||||
|
.then(buf => Utils.getImageMetadata(buf))
|
||||||
|
.then(meta => ({ ...meta, position: 0, sourceUrl: input.url, type: input.type } as ImageInfo))
|
||||||
|
)
|
||||||
|
|
||||||
|
export const createBoxArt = fn(
|
||||||
|
z.object({
|
||||||
|
backgroundUrl: z.string(),
|
||||||
|
logoUrl: z.string(),
|
||||||
|
}),
|
||||||
|
async (input) =>
|
||||||
|
Utils.createBoxArtBuffer(input.logoUrl, input.backgroundUrl)
|
||||||
|
.then(buf => Utils.getImageMetadata(buf))
|
||||||
|
.then(meta => ({ ...meta, position: 0, sourceUrl: null, type: 'boxArt' as const }) as ImageInfo)
|
||||||
|
)
|
||||||
|
|
||||||
|
export const createHeroArt = fn(
|
||||||
|
z.object({
|
||||||
|
screenshots: z.string().array(),
|
||||||
|
backdropUrl: z.string()
|
||||||
|
}),
|
||||||
|
async (input) => {
|
||||||
|
// Download screenshot buffers in parallel
|
||||||
const shots: Shot[] = await Promise.all(
|
const shots: Shot[] = await Promise.all(
|
||||||
screenshotUrls.map(async url => ({ url, buffer: await Utils.fetchBuffer(url) }))
|
input.screenshots.map(async url => ({ url, buffer: await Utils.fetchBuffer(url) }))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const baselineBuffer = await Utils.fetchBuffer(input.backdropUrl);
|
||||||
|
|
||||||
// 4. Score screenshots (or pick single)
|
// 4. Score screenshots (or pick single)
|
||||||
const scores =
|
const scores =
|
||||||
shots.length === 1
|
shots.length === 1
|
||||||
@@ -204,37 +248,69 @@ export namespace Client {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5b. Asset images
|
const settled = await Promise.allSettled(tasks);
|
||||||
for (const [type, url] of Object.entries({ ...assetUrls, icon: iconUrl })) {
|
|
||||||
if (!url || type === "backdrop") continue;
|
|
||||||
tasks.push(
|
|
||||||
Utils.fetchBuffer(url)
|
|
||||||
.then(buf => Utils.getImageMetadata(buf))
|
|
||||||
.then(meta => ({ ...meta, position: 0, sourceUrl: url, type: type as ImageType } as ImageInfo))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5c. Backdrop
|
|
||||||
tasks.push(
|
|
||||||
Utils.getImageMetadata(baselineBuffer)
|
|
||||||
.then(meta => ({ ...meta, position: 0, sourceUrl: assetUrls.backdrop, type: "backdrop" as const } as ImageInfo))
|
|
||||||
)
|
|
||||||
|
|
||||||
// 5d. Box art
|
|
||||||
tasks.push(
|
|
||||||
Utils.createBoxArtBuffer(game.library_assets_full, appid)
|
|
||||||
.then(buf => Utils.getImageMetadata(buf))
|
|
||||||
.then(meta => ({ ...meta, position: 0, sourceUrl: null, type: 'boxArt' as const }) as ImageInfo)
|
|
||||||
);
|
|
||||||
|
|
||||||
const settled = await Promise.allSettled(tasks)
|
|
||||||
|
|
||||||
settled
|
settled
|
||||||
.filter(r => r.status === "rejected")
|
.filter(r => r.status === "rejected")
|
||||||
.forEach(r => console.warn("[getImages] failed:", (r as PromiseRejectedResult).reason));
|
.forEach(r => console.warn("[getHeroArt] failed:", (r as PromiseRejectedResult).reason));
|
||||||
|
|
||||||
// 6. Await all and return
|
// Await all and return
|
||||||
return settled.filter(s => s.status === "fulfilled").map(r => (r as PromiseFulfilledResult<ImageInfo>).value)
|
return settled.filter(s => s.status === "fulfilled").map(r => (r as PromiseFulfilledResult<ImageInfo>).value)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies a Steam OpenID response by sending a request back to Steam
|
||||||
|
* with mode=check_authentication
|
||||||
|
*/
|
||||||
|
export async function verifyOpenIDResponse(params: URLSearchParams): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
// Create a new URLSearchParams with all the original parameters
|
||||||
|
const verificationParams = new URLSearchParams();
|
||||||
|
|
||||||
|
// Copy all parameters from the original request
|
||||||
|
for (const [key, value] of params.entries()) {
|
||||||
|
verificationParams.append(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change mode to check_authentication for verification
|
||||||
|
verificationParams.set('openid.mode', 'check_authentication');
|
||||||
|
|
||||||
|
// Send verification request to Steam
|
||||||
|
const verificationResponse = await fetch('https://steamcommunity.com/openid/login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
},
|
||||||
|
body: verificationParams.toString()
|
||||||
|
});
|
||||||
|
|
||||||
|
const responseText = await verificationResponse.text();
|
||||||
|
|
||||||
|
// Check if verification was successful
|
||||||
|
if (!responseText.includes('is_valid:true')) {
|
||||||
|
console.error('OpenID verification failed: Invalid response from Steam', responseText);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract steamID from the claimed_id
|
||||||
|
const claimedId = params.get('openid.claimed_id');
|
||||||
|
if (!claimedId) {
|
||||||
|
console.error('OpenID verification failed: Missing claimed_id');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the Steam ID from the claimed_id
|
||||||
|
const steamID = claimedId.split('/').pop();
|
||||||
|
if (!steamID || !/^\d+$/.test(steamID)) {
|
||||||
|
console.error('OpenID verification failed: Invalid steamID format', steamID);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return steamID;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('OpenID verification error:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
544
packages/core/src/client/steam.ts
Normal file
544
packages/core/src/client/steam.ts
Normal file
@@ -0,0 +1,544 @@
|
|||||||
|
export namespace Steam {
|
||||||
|
//Source: https://github.com/woctezuma/steam-api/blob/master/data/genres.json
|
||||||
|
export const genres = {
|
||||||
|
"1": "Action",
|
||||||
|
"2": "Strategy",
|
||||||
|
"3": "RPG",
|
||||||
|
"4": "Casual",
|
||||||
|
"9": "Racing",
|
||||||
|
"18": "Sports",
|
||||||
|
"23": "Indie",
|
||||||
|
"25": "Adventure",
|
||||||
|
"28": "Simulation",
|
||||||
|
"29": "Massively Multiplayer",
|
||||||
|
"37": "Free to Play",
|
||||||
|
"50": "Accounting",
|
||||||
|
"51": "Animation & Modeling",
|
||||||
|
"52": "Audio Production",
|
||||||
|
"53": "Design & Illustration",
|
||||||
|
"54": "Education",
|
||||||
|
"55": "Photo Editing",
|
||||||
|
"56": "Software Training",
|
||||||
|
"57": "Utilities",
|
||||||
|
"58": "Video Production",
|
||||||
|
"59": "Web Publishing",
|
||||||
|
"60": "Game Development",
|
||||||
|
"70": "Early Access",
|
||||||
|
"71": "Sexual Content",
|
||||||
|
"72": "Nudity",
|
||||||
|
"73": "Violent",
|
||||||
|
"74": "Gore",
|
||||||
|
"80": "Movie",
|
||||||
|
"81": "Documentary",
|
||||||
|
"82": "Episodic",
|
||||||
|
"83": "Short",
|
||||||
|
"84": "Tutorial",
|
||||||
|
"85": "360 Video"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Source: https://github.com/woctezuma/steam-api/blob/master/data/categories.json
|
||||||
|
export const categories = {
|
||||||
|
"1": "Multi-player",
|
||||||
|
"2": "Single-player",
|
||||||
|
"6": "Mods (require HL2)",
|
||||||
|
"7": "Mods (require HL1)",
|
||||||
|
"8": "Valve Anti-Cheat enabled",
|
||||||
|
"9": "Co-op",
|
||||||
|
"10": "Demos",
|
||||||
|
"12": "HDR available",
|
||||||
|
"13": "Captions available",
|
||||||
|
"14": "Commentary available",
|
||||||
|
"15": "Stats",
|
||||||
|
"16": "Includes Source SDK",
|
||||||
|
"17": "Includes level editor",
|
||||||
|
"18": "Partial Controller Support",
|
||||||
|
"19": "Mods",
|
||||||
|
"20": "MMO",
|
||||||
|
"21": "Downloadable Content",
|
||||||
|
"22": "Steam Achievements",
|
||||||
|
"23": "Steam Cloud",
|
||||||
|
"24": "Shared/Split Screen",
|
||||||
|
"25": "Steam Leaderboards",
|
||||||
|
"27": "Cross-Platform Multiplayer",
|
||||||
|
"28": "Full controller support",
|
||||||
|
"29": "Steam Trading Cards",
|
||||||
|
"30": "Steam Workshop",
|
||||||
|
"31": "VR Support",
|
||||||
|
"32": "Steam Turn Notifications",
|
||||||
|
"33": "Native Steam Controller",
|
||||||
|
"35": "In-App Purchases",
|
||||||
|
"36": "Online PvP",
|
||||||
|
"37": "Shared/Split Screen PvP",
|
||||||
|
"38": "Online Co-op",
|
||||||
|
"39": "Shared/Split Screen Co-op",
|
||||||
|
"40": "SteamVR Collectibles",
|
||||||
|
"41": "Remote Play on Phone",
|
||||||
|
"42": "Remote Play on Tablet",
|
||||||
|
"43": "Remote Play on TV",
|
||||||
|
"44": "Remote Play Together",
|
||||||
|
"45": "Cloud Gaming",
|
||||||
|
"46": "Cloud Gaming (NVIDIA)",
|
||||||
|
"47": "LAN PvP",
|
||||||
|
"48": "LAN Co-op",
|
||||||
|
"49": "PvP",
|
||||||
|
"50": "Additional High-Quality Audio",
|
||||||
|
"51": "Steam Workshop",
|
||||||
|
"52": "Tracked Controller Support",
|
||||||
|
"53": "VR Supported",
|
||||||
|
"54": "VR Only"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source: https://files.catbox.moe/96bty7.json
|
||||||
|
export const tags = {
|
||||||
|
"9": "Strategy",
|
||||||
|
"19": "Action",
|
||||||
|
"21": "Adventure",
|
||||||
|
"84": "Design & Illustration",
|
||||||
|
"87": "Utilities",
|
||||||
|
"113": "Free to Play",
|
||||||
|
"122": "RPG",
|
||||||
|
"128": "Massively Multiplayer",
|
||||||
|
"492": "Indie",
|
||||||
|
"493": "Early Access",
|
||||||
|
"597": "Casual",
|
||||||
|
"599": "Simulation",
|
||||||
|
"699": "Racing",
|
||||||
|
"701": "Sports",
|
||||||
|
"784": "Video Production",
|
||||||
|
"809": "Photo Editing",
|
||||||
|
"872": "Animation & Modeling",
|
||||||
|
"1027": "Audio Production",
|
||||||
|
"1036": "Education",
|
||||||
|
"1038": "Web Publishing",
|
||||||
|
"1445": "Software Training",
|
||||||
|
"1616": "Trains",
|
||||||
|
"1621": "Music",
|
||||||
|
"1625": "Platformer",
|
||||||
|
"1628": "Metroidvania",
|
||||||
|
"1638": "Dog",
|
||||||
|
"1643": "Building",
|
||||||
|
"1644": "Driving",
|
||||||
|
"1645": "Tower Defense",
|
||||||
|
"1646": "Hack and Slash",
|
||||||
|
"1647": "Western",
|
||||||
|
"1649": "GameMaker",
|
||||||
|
"1651": "Satire",
|
||||||
|
"1654": "Relaxing",
|
||||||
|
"1659": "Zombies",
|
||||||
|
"1662": "Survival",
|
||||||
|
"1663": "FPS",
|
||||||
|
"1664": "Puzzle",
|
||||||
|
"1665": "Match 3",
|
||||||
|
"1666": "Card Game",
|
||||||
|
"1667": "Horror",
|
||||||
|
"1669": "Moddable",
|
||||||
|
"1670": "4X",
|
||||||
|
"1671": "Superhero",
|
||||||
|
"1673": "Aliens",
|
||||||
|
"1674": "Typing",
|
||||||
|
"1676": "RTS",
|
||||||
|
"1677": "Turn-Based",
|
||||||
|
"1678": "War",
|
||||||
|
"1680": "Heist",
|
||||||
|
"1681": "Pirates",
|
||||||
|
"1684": "Fantasy",
|
||||||
|
"1685": "Co-op",
|
||||||
|
"1687": "Stealth",
|
||||||
|
"1688": "Ninja",
|
||||||
|
"1693": "Classic",
|
||||||
|
"1695": "Open World",
|
||||||
|
"1697": "Third Person",
|
||||||
|
"1698": "Point & Click",
|
||||||
|
"1702": "Crafting",
|
||||||
|
"1708": "Tactical",
|
||||||
|
"1710": "Surreal",
|
||||||
|
"1714": "Psychedelic",
|
||||||
|
"1716": "Roguelike",
|
||||||
|
"1717": "Hex Grid",
|
||||||
|
"1718": "MOBA",
|
||||||
|
"1719": "Comedy",
|
||||||
|
"1720": "Dungeon Crawler",
|
||||||
|
"1721": "Psychological Horror",
|
||||||
|
"1723": "Action RTS",
|
||||||
|
"1730": "Sokoban",
|
||||||
|
"1732": "Voxel",
|
||||||
|
"1733": "Unforgiving",
|
||||||
|
"1734": "Fast-Paced",
|
||||||
|
"1736": "LEGO",
|
||||||
|
"1738": "Hidden Object",
|
||||||
|
"1741": "Turn-Based Strategy",
|
||||||
|
"1742": "Story Rich",
|
||||||
|
"1743": "Fighting",
|
||||||
|
"1746": "Basketball",
|
||||||
|
"1751": "Comic Book",
|
||||||
|
"1752": "Rhythm",
|
||||||
|
"1753": "Skateboarding",
|
||||||
|
"1754": "MMORPG",
|
||||||
|
"1755": "Space",
|
||||||
|
"1756": "Great Soundtrack",
|
||||||
|
"1759": "Perma Death",
|
||||||
|
"1770": "Board Game",
|
||||||
|
"1773": "Arcade",
|
||||||
|
"1774": "Shooter",
|
||||||
|
"1775": "PvP",
|
||||||
|
"1777": "Steampunk",
|
||||||
|
"3796": "Based On A Novel",
|
||||||
|
"3798": "Side Scroller",
|
||||||
|
"3799": "Visual Novel",
|
||||||
|
"3810": "Sandbox",
|
||||||
|
"3813": "Real Time Tactics",
|
||||||
|
"3814": "Third-Person Shooter",
|
||||||
|
"3834": "Exploration",
|
||||||
|
"3835": "Post-apocalyptic",
|
||||||
|
"3839": "First-Person",
|
||||||
|
"3841": "Local Co-Op",
|
||||||
|
"3843": "Online Co-Op",
|
||||||
|
"3854": "Lore-Rich",
|
||||||
|
"3859": "Multiplayer",
|
||||||
|
"3871": "2D",
|
||||||
|
"3877": "Precision Platformer",
|
||||||
|
"3878": "Competitive",
|
||||||
|
"3916": "Old School",
|
||||||
|
"3920": "Cooking",
|
||||||
|
"3934": "Immersive",
|
||||||
|
"3942": "Sci-fi",
|
||||||
|
"3952": "Gothic",
|
||||||
|
"3955": "Character Action Game",
|
||||||
|
"3959": "Roguelite",
|
||||||
|
"3964": "Pixel Graphics",
|
||||||
|
"3965": "Epic",
|
||||||
|
"3968": "Physics",
|
||||||
|
"3978": "Survival Horror",
|
||||||
|
"3987": "Historical",
|
||||||
|
"3993": "Combat",
|
||||||
|
"4004": "Retro",
|
||||||
|
"4018": "Vampire",
|
||||||
|
"4026": "Difficult",
|
||||||
|
"4036": "Parkour",
|
||||||
|
"4046": "Dragons",
|
||||||
|
"4057": "Magic",
|
||||||
|
"4064": "Thriller",
|
||||||
|
"4085": "Anime",
|
||||||
|
"4094": "Minimalist",
|
||||||
|
"4102": "Combat Racing",
|
||||||
|
"4106": "Action-Adventure",
|
||||||
|
"4115": "Cyberpunk",
|
||||||
|
"4136": "Funny",
|
||||||
|
"4137": "Transhumanism",
|
||||||
|
"4145": "Cinematic",
|
||||||
|
"4150": "World War II",
|
||||||
|
"4155": "Class-Based",
|
||||||
|
"4158": "Beat 'em up",
|
||||||
|
"4161": "Real-Time",
|
||||||
|
"4166": "Atmospheric",
|
||||||
|
"4168": "Military",
|
||||||
|
"4172": "Medieval",
|
||||||
|
"4175": "Realistic",
|
||||||
|
"4182": "Singleplayer",
|
||||||
|
"4184": "Chess",
|
||||||
|
"4190": "Addictive",
|
||||||
|
"4191": "3D",
|
||||||
|
"4195": "Cartoony",
|
||||||
|
"4202": "Trading",
|
||||||
|
"4231": "Action RPG",
|
||||||
|
"4234": "Short",
|
||||||
|
"4236": "Loot",
|
||||||
|
"4242": "Episodic",
|
||||||
|
"4252": "Stylized",
|
||||||
|
"4255": "Shoot 'Em Up",
|
||||||
|
"4291": "Spaceships",
|
||||||
|
"4295": "Futuristic",
|
||||||
|
"4305": "Colorful",
|
||||||
|
"4325": "Turn-Based Combat",
|
||||||
|
"4328": "City Builder",
|
||||||
|
"4342": "Dark",
|
||||||
|
"4345": "Gore",
|
||||||
|
"4364": "Grand Strategy",
|
||||||
|
"4376": "Assassin",
|
||||||
|
"4400": "Abstract",
|
||||||
|
"4434": "JRPG",
|
||||||
|
"4474": "CRPG",
|
||||||
|
"4486": "Choose Your Own Adventure",
|
||||||
|
"4508": "Co-op Campaign",
|
||||||
|
"4520": "Farming",
|
||||||
|
"4559": "Quick-Time Events",
|
||||||
|
"4562": "Cartoon",
|
||||||
|
"4598": "Alternate History",
|
||||||
|
"4604": "Dark Fantasy",
|
||||||
|
"4608": "Swordplay",
|
||||||
|
"4637": "Top-Down Shooter",
|
||||||
|
"4667": "Violent",
|
||||||
|
"4684": "Wargame",
|
||||||
|
"4695": "Economy",
|
||||||
|
"4700": "Movie",
|
||||||
|
"4711": "Replay Value",
|
||||||
|
"4726": "Cute",
|
||||||
|
"4736": "2D Fighter",
|
||||||
|
"4747": "Character Customization",
|
||||||
|
"4754": "Politics",
|
||||||
|
"4758": "Twin Stick Shooter",
|
||||||
|
"4777": "Spectacle fighter",
|
||||||
|
"4791": "Top-Down",
|
||||||
|
"4821": "Mechs",
|
||||||
|
"4835": "6DOF",
|
||||||
|
"4840": "4 Player Local",
|
||||||
|
"4845": "Capitalism",
|
||||||
|
"4853": "Political",
|
||||||
|
"4878": "Parody",
|
||||||
|
"4885": "Bullet Hell",
|
||||||
|
"4947": "Romance",
|
||||||
|
"4975": "2.5D",
|
||||||
|
"4994": "Naval Combat",
|
||||||
|
"5030": "Dystopian",
|
||||||
|
"5055": "eSports",
|
||||||
|
"5094": "Narration",
|
||||||
|
"5125": "Procedural Generation",
|
||||||
|
"5153": "Kickstarter",
|
||||||
|
"5154": "Score Attack",
|
||||||
|
"5160": "Dinosaurs",
|
||||||
|
"5179": "Cold War",
|
||||||
|
"5186": "Psychological",
|
||||||
|
"5228": "Blood",
|
||||||
|
"5230": "Sequel",
|
||||||
|
"5300": "God Game",
|
||||||
|
"5310": "Games Workshop",
|
||||||
|
"5348": "Mod",
|
||||||
|
"5350": "Family Friendly",
|
||||||
|
"5363": "Destruction",
|
||||||
|
"5372": "Conspiracy",
|
||||||
|
"5379": "2D Platformer",
|
||||||
|
"5382": "World War I",
|
||||||
|
"5390": "Time Attack",
|
||||||
|
"5395": "3D Platformer",
|
||||||
|
"5407": "Benchmark",
|
||||||
|
"5411": "Beautiful",
|
||||||
|
"5432": "Programming",
|
||||||
|
"5502": "Hacking",
|
||||||
|
"5537": "Puzzle Platformer",
|
||||||
|
"5547": "Arena Shooter",
|
||||||
|
"5577": "RPGMaker",
|
||||||
|
"5608": "Emotional",
|
||||||
|
"5611": "Mature",
|
||||||
|
"5613": "Detective",
|
||||||
|
"5652": "Collectathon",
|
||||||
|
"5673": "Modern",
|
||||||
|
"5708": "Remake",
|
||||||
|
"5711": "Team-Based",
|
||||||
|
"5716": "Mystery",
|
||||||
|
"5727": "Baseball",
|
||||||
|
"5752": "Robots",
|
||||||
|
"5765": "Gun Customization",
|
||||||
|
"5794": "Science",
|
||||||
|
"5796": "Bullet Time",
|
||||||
|
"5851": "Isometric",
|
||||||
|
"5900": "Walking Simulator",
|
||||||
|
"5914": "Tennis",
|
||||||
|
"5923": "Dark Humor",
|
||||||
|
"5941": "Reboot",
|
||||||
|
"5981": "Mining",
|
||||||
|
"5984": "Drama",
|
||||||
|
"6041": "Horses",
|
||||||
|
"6052": "Noir",
|
||||||
|
"6129": "Logic",
|
||||||
|
"6214": "Birds",
|
||||||
|
"6276": "Inventory Management",
|
||||||
|
"6310": "Diplomacy",
|
||||||
|
"6378": "Crime",
|
||||||
|
"6426": "Choices Matter",
|
||||||
|
"6506": "3D Fighter",
|
||||||
|
"6621": "Pinball",
|
||||||
|
"6625": "Time Manipulation",
|
||||||
|
"6650": "Nudity",
|
||||||
|
"6691": "1990's",
|
||||||
|
"6702": "Mars",
|
||||||
|
"6730": "PvE",
|
||||||
|
"6815": "Hand-drawn",
|
||||||
|
"6869": "Nonlinear",
|
||||||
|
"6910": "Naval",
|
||||||
|
"6915": "Martial Arts",
|
||||||
|
"6948": "Rome",
|
||||||
|
"6971": "Multiple Endings",
|
||||||
|
"7038": "Golf",
|
||||||
|
"7107": "Real-Time with Pause",
|
||||||
|
"7108": "Party",
|
||||||
|
"7113": "Crowdfunded",
|
||||||
|
"7178": "Party Game",
|
||||||
|
"7208": "Female Protagonist",
|
||||||
|
"7250": "Linear",
|
||||||
|
"7309": "Skiing",
|
||||||
|
"7328": "Bowling",
|
||||||
|
"7332": "Base Building",
|
||||||
|
"7368": "Local Multiplayer",
|
||||||
|
"7423": "Sniper",
|
||||||
|
"7432": "Lovecraftian",
|
||||||
|
"7478": "Illuminati",
|
||||||
|
"7481": "Controller",
|
||||||
|
"7556": "Dice",
|
||||||
|
"7569": "Grid-Based Movement",
|
||||||
|
"7622": "Offroad",
|
||||||
|
"7702": "Narrative",
|
||||||
|
"7743": "1980s",
|
||||||
|
"7782": "Cult Classic",
|
||||||
|
"7918": "Dwarf",
|
||||||
|
"7926": "Artificial Intelligence",
|
||||||
|
"7948": "Soundtrack",
|
||||||
|
"8013": "Software",
|
||||||
|
"8075": "TrackIR",
|
||||||
|
"8093": "Minigames",
|
||||||
|
"8122": "Level Editor",
|
||||||
|
"8253": "Music-Based Procedural Generation",
|
||||||
|
"8369": "Investigation",
|
||||||
|
"8461": "Well-Written",
|
||||||
|
"8666": "Runner",
|
||||||
|
"8945": "Resource Management",
|
||||||
|
"9130": "Hentai",
|
||||||
|
"9157": "Underwater",
|
||||||
|
"9204": "Immersive Sim",
|
||||||
|
"9271": "Trading Card Game",
|
||||||
|
"9541": "Demons",
|
||||||
|
"9551": "Dating Sim",
|
||||||
|
"9564": "Hunting",
|
||||||
|
"9592": "Dynamic Narration",
|
||||||
|
"9803": "Snow",
|
||||||
|
"9994": "Experience",
|
||||||
|
"10235": "Life Sim",
|
||||||
|
"10383": "Transportation",
|
||||||
|
"10397": "Memes",
|
||||||
|
"10437": "Trivia",
|
||||||
|
"10679": "Time Travel",
|
||||||
|
"10695": "Party-Based RPG",
|
||||||
|
"10808": "Supernatural",
|
||||||
|
"10816": "Split Screen",
|
||||||
|
"11014": "Interactive Fiction",
|
||||||
|
"11095": "Boss Rush",
|
||||||
|
"11104": "Vehicular Combat",
|
||||||
|
"11123": "Mouse only",
|
||||||
|
"11333": "Villain Protagonist",
|
||||||
|
"11634": "Vikings",
|
||||||
|
"12057": "Tutorial",
|
||||||
|
"12095": "Sexual Content",
|
||||||
|
"12190": "Boxing",
|
||||||
|
"12286": "Warhammer 40K",
|
||||||
|
"12472": "Management",
|
||||||
|
"13070": "Solitaire",
|
||||||
|
"13190": "America",
|
||||||
|
"13276": "Tanks",
|
||||||
|
"13382": "Archery",
|
||||||
|
"13577": "Sailing",
|
||||||
|
"13782": "Experimental",
|
||||||
|
"13906": "Game Development",
|
||||||
|
"14139": "Turn-Based Tactics",
|
||||||
|
"14153": "Dungeons & Dragons",
|
||||||
|
"14720": "Nostalgia",
|
||||||
|
"14906": "Intentionally Awkward Controls",
|
||||||
|
"15045": "Flight",
|
||||||
|
"15172": "Conversation",
|
||||||
|
"15277": "Philosophical",
|
||||||
|
"15339": "Documentary",
|
||||||
|
"15564": "Fishing",
|
||||||
|
"15868": "Motocross",
|
||||||
|
"15954": "Silent Protagonist",
|
||||||
|
"16094": "Mythology",
|
||||||
|
"16250": "Gambling",
|
||||||
|
"16598": "Space Sim",
|
||||||
|
"16689": "Time Management",
|
||||||
|
"17015": "Werewolves",
|
||||||
|
"17305": "Strategy RPG",
|
||||||
|
"17337": "Lemmings",
|
||||||
|
"17389": "Tabletop",
|
||||||
|
"17770": "Asynchronous Multiplayer",
|
||||||
|
"17894": "Cats",
|
||||||
|
"17927": "Pool",
|
||||||
|
"18594": "FMV",
|
||||||
|
"19568": "Cycling",
|
||||||
|
"19780": "Submarine",
|
||||||
|
"19995": "Dark Comedy",
|
||||||
|
"21006": "Underground",
|
||||||
|
"21491": "Demo Available",
|
||||||
|
"21725": "Tactical RPG",
|
||||||
|
"21978": "VR",
|
||||||
|
"22602": "Agriculture",
|
||||||
|
"22955": "Mini Golf",
|
||||||
|
"24003": "Word Game",
|
||||||
|
"24904": "NSFW",
|
||||||
|
"25085": "Touch-Friendly",
|
||||||
|
"26921": "Political Sim",
|
||||||
|
"27758": "Voice Control",
|
||||||
|
"28444": "Snowboarding",
|
||||||
|
"29363": "3D Vision",
|
||||||
|
"29482": "Souls-like",
|
||||||
|
"29855": "Ambient",
|
||||||
|
"30358": "Nature",
|
||||||
|
"30927": "Fox",
|
||||||
|
"31275": "Text-Based",
|
||||||
|
"31579": "Otome",
|
||||||
|
"32322": "Deckbuilding",
|
||||||
|
"33572": "Mahjong",
|
||||||
|
"35079": "Job Simulator",
|
||||||
|
"42089": "Jump Scare",
|
||||||
|
"42329": "Coding",
|
||||||
|
"42804": "Action Roguelike",
|
||||||
|
"44868": "LGBTQ+",
|
||||||
|
"47827": "Wrestling",
|
||||||
|
"49213": "Rugby",
|
||||||
|
"51306": "Foreign",
|
||||||
|
"56690": "On-Rails Shooter",
|
||||||
|
"61357": "Electronic Music",
|
||||||
|
"65443": "Adult Content",
|
||||||
|
"71389": "Spelling",
|
||||||
|
"87918": "Farming Sim",
|
||||||
|
"91114": "Shop Keeper",
|
||||||
|
"92092": "Jet",
|
||||||
|
"96359": "Skating",
|
||||||
|
"97376": "Cozy",
|
||||||
|
"102530": "Elf",
|
||||||
|
"117648": "8-bit Music",
|
||||||
|
"123332": "Bikes",
|
||||||
|
"129761": "ATV",
|
||||||
|
"143739": "Electronic",
|
||||||
|
"150626": "Gaming",
|
||||||
|
"158638": "Cricket",
|
||||||
|
"176981": "Battle Royale",
|
||||||
|
"180368": "Faith",
|
||||||
|
"189941": "Instrumental Music",
|
||||||
|
"198631": "Mystery Dungeon",
|
||||||
|
"198913": "Motorbike",
|
||||||
|
"220585": "Colony Sim",
|
||||||
|
"233824": "Feature Film",
|
||||||
|
"252854": "BMX",
|
||||||
|
"255534": "Automation",
|
||||||
|
"323922": "Musou",
|
||||||
|
"324176": "Hockey",
|
||||||
|
"337964": "Rock Music",
|
||||||
|
"348922": "Steam Machine",
|
||||||
|
"353880": "Looter Shooter",
|
||||||
|
"363767": "Snooker",
|
||||||
|
"379975": "Clicker",
|
||||||
|
"454187": "Traditional Roguelike",
|
||||||
|
"552282": "Wholesome",
|
||||||
|
"603297": "Hardware",
|
||||||
|
"615955": "Idler",
|
||||||
|
"620519": "Hero Shooter",
|
||||||
|
"745697": "Social Deduction",
|
||||||
|
"769306": "Escape Room",
|
||||||
|
"776177": "360 Video",
|
||||||
|
"791774": "Card Battler",
|
||||||
|
"847164": "Volleyball",
|
||||||
|
"856791": "Asymmetric VR",
|
||||||
|
"916648": "Creature Collector",
|
||||||
|
"922563": "Roguevania",
|
||||||
|
"1003823": "Profile Features Limited",
|
||||||
|
"1023537": "Boomer Shooter",
|
||||||
|
"1084988": "Auto Battler",
|
||||||
|
"1091588": "Roguelike Deckbuilder",
|
||||||
|
"1100686": "Outbreak Sim",
|
||||||
|
"1100687": "Automobile Sim",
|
||||||
|
"1100688": "Medical Sim",
|
||||||
|
"1100689": "Open World Survival Craft",
|
||||||
|
"1199779": "Extraction Shooter",
|
||||||
|
"1220528": "Hobby Sim",
|
||||||
|
"1254546": "Football (Soccer)",
|
||||||
|
"1254552": "Football (American)",
|
||||||
|
"1368160": "AI Content Disclosed",
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -284,16 +284,27 @@ export type GenreType = {
|
|||||||
export interface AppInfo {
|
export interface AppInfo {
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
images: {
|
||||||
|
logo: string;
|
||||||
|
backdrop: string;
|
||||||
|
poster: string;
|
||||||
|
banner: string;
|
||||||
|
screenshots: string[];
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
links: string[] | null;
|
||||||
score: number;
|
score: number;
|
||||||
gameid: string;
|
id: string;
|
||||||
releaseDate: Date;
|
releaseDate: Date;
|
||||||
description: string;
|
description: string | null;
|
||||||
compatibility: "low" | "mid" | "high" | "unknown";
|
compatibility: "low" | "mid" | "high" | "unknown";
|
||||||
controllerSupport: "partial" | "full" | "unknown";
|
controllerSupport: "partial" | "full" | "unknown";
|
||||||
primaryGenre: string | null;
|
primaryGenre: string | null;
|
||||||
size: { downloadSize: number; sizeOnDisk: number };
|
size: { downloadSize: number; sizeOnDisk: number };
|
||||||
tags: Array<{ name: string; slug: string; type: "tag" }>;
|
tags: Array<{ name: string; slug: string; type: "tag" }>;
|
||||||
genres: Array<{ type: "genre"; name: string; slug: string }>;
|
genres: Array<{ type: "genre"; name: string; slug: string }>;
|
||||||
|
categories: Array<{ name: string; slug: string; type: "categorie" }>;
|
||||||
|
franchises: Array<{ name: string; slug: string; type: "franchise" }>;
|
||||||
developers: Array<{ name: string; slug: string; type: "developer" }>;
|
developers: Array<{ name: string; slug: string; type: "developer" }>;
|
||||||
publishers: Array<{ name: string; slug: string; type: "publisher" }>;
|
publishers: Array<{ name: string; slug: string; type: "publisher" }>;
|
||||||
}
|
}
|
||||||
@@ -342,3 +353,248 @@ export interface RankedShot {
|
|||||||
url: string;
|
url: string;
|
||||||
score: number;
|
score: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SteamPlayerSummaryResponse {
|
||||||
|
response: {
|
||||||
|
players: SteamPlayerSummary[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SteamPlayerSummary {
|
||||||
|
steamid: string;
|
||||||
|
communityvisibilitystate: number;
|
||||||
|
profilestate?: number;
|
||||||
|
personaname: string;
|
||||||
|
profileurl: string;
|
||||||
|
avatar: string;
|
||||||
|
avatarmedium: string;
|
||||||
|
avatarfull: string;
|
||||||
|
avatarhash: string;
|
||||||
|
lastlogoff?: number;
|
||||||
|
personastate: number;
|
||||||
|
realname?: string;
|
||||||
|
primaryclanid?: string;
|
||||||
|
timecreated: number;
|
||||||
|
personastateflags?: number;
|
||||||
|
loccountrycode?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SteamPlayerBansResponse {
|
||||||
|
players: SteamPlayerBan[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SteamPlayerBan {
|
||||||
|
SteamId: string;
|
||||||
|
CommunityBanned: boolean;
|
||||||
|
VACBanned: boolean;
|
||||||
|
NumberOfVACBans: number;
|
||||||
|
DaysSinceLastBan: number;
|
||||||
|
NumberOfGameBans: number;
|
||||||
|
EconomyBan: 'none' | 'probation' | 'banned'; // Enum based on known possible values
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SteamAccount = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
realName: string | null;
|
||||||
|
steamMemberSince: Date;
|
||||||
|
avatarHash: string;
|
||||||
|
limitations: {
|
||||||
|
isLimited: boolean;
|
||||||
|
tradeBanState: 'none' | 'probation' | 'banned';
|
||||||
|
isVacBanned: boolean;
|
||||||
|
visibilityState: number;
|
||||||
|
privacyState: 'public' | 'private' | 'friendsonly';
|
||||||
|
};
|
||||||
|
profileUrl: string;
|
||||||
|
lastSyncedAt: Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface SteamFriendsListResponse {
|
||||||
|
friendslist: {
|
||||||
|
friends: SteamFriend[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SteamFriend {
|
||||||
|
steamid: string;
|
||||||
|
relationship: 'friend'; // could expand this if Steam ever adds more types
|
||||||
|
friend_since: number; // Unix timestamp (seconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SteamOwnedGamesResponse {
|
||||||
|
response: {
|
||||||
|
game_count: number;
|
||||||
|
games: SteamOwnedGame[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SteamOwnedGame {
|
||||||
|
appid: number;
|
||||||
|
name: string;
|
||||||
|
playtime_forever: number;
|
||||||
|
img_icon_url: string;
|
||||||
|
|
||||||
|
playtime_windows_forever?: number;
|
||||||
|
playtime_mac_forever?: number;
|
||||||
|
playtime_linux_forever?: number;
|
||||||
|
playtime_deck_forever?: number;
|
||||||
|
|
||||||
|
rtime_last_played?: number; // Unix timestamp
|
||||||
|
content_descriptorids?: number[];
|
||||||
|
playtime_disconnected?: number;
|
||||||
|
has_community_visible_stats?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The shape of the parsed Steam profile information.
|
||||||
|
*/
|
||||||
|
export interface ProfileInfo {
|
||||||
|
steamID64: string;
|
||||||
|
isLimited: boolean;
|
||||||
|
privacyState: 'public' | 'private' | 'friendsonly' | string;
|
||||||
|
visibility: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SteamStoreResponse {
|
||||||
|
response: {
|
||||||
|
store_items: SteamStoreItem[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SteamStoreItem {
|
||||||
|
item_type: number;
|
||||||
|
id: number;
|
||||||
|
success: number;
|
||||||
|
visible: boolean;
|
||||||
|
name: string;
|
||||||
|
store_url_path: string;
|
||||||
|
appid: number;
|
||||||
|
type: number;
|
||||||
|
tagids: number[];
|
||||||
|
categories: {
|
||||||
|
supported_player_categoryids?: number[];
|
||||||
|
feature_categoryids?: number[];
|
||||||
|
controller_categoryids?: number[];
|
||||||
|
};
|
||||||
|
reviews: {
|
||||||
|
summary_filtered: {
|
||||||
|
review_count: number;
|
||||||
|
percent_positive: number;
|
||||||
|
review_score: number;
|
||||||
|
review_score_label: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
basic_info: {
|
||||||
|
short_description?: string;
|
||||||
|
publishers: SteamCreator[];
|
||||||
|
developers: SteamCreator[];
|
||||||
|
franchises?: SteamCreator[];
|
||||||
|
};
|
||||||
|
tags: {
|
||||||
|
tagid: number;
|
||||||
|
weight: number;
|
||||||
|
}[];
|
||||||
|
assets: SteamAssets;
|
||||||
|
assets_without_overrides: SteamAssets;
|
||||||
|
release: {
|
||||||
|
steam_release_date: number;
|
||||||
|
};
|
||||||
|
platforms: {
|
||||||
|
windows: boolean;
|
||||||
|
mac: boolean;
|
||||||
|
steamos_linux: boolean;
|
||||||
|
vr_support: Record<string, never>;
|
||||||
|
steam_deck_compat_category?: number;
|
||||||
|
steam_os_compat_category?: number;
|
||||||
|
};
|
||||||
|
best_purchase_option: PurchaseOption;
|
||||||
|
purchase_options: PurchaseOption[];
|
||||||
|
screenshots: {
|
||||||
|
all_ages_screenshots: {
|
||||||
|
filename: string;
|
||||||
|
ordinal: number;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
trailers: {
|
||||||
|
highlights: Trailer[];
|
||||||
|
};
|
||||||
|
supported_languages: SupportedLanguage[];
|
||||||
|
full_description: string;
|
||||||
|
links?: {
|
||||||
|
link_type: number;
|
||||||
|
url: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SteamCreator {
|
||||||
|
name: string;
|
||||||
|
creator_clan_account_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SteamAssets {
|
||||||
|
asset_url_format: string;
|
||||||
|
main_capsule: string;
|
||||||
|
small_capsule: string;
|
||||||
|
header: string;
|
||||||
|
page_background: string;
|
||||||
|
hero_capsule: string;
|
||||||
|
hero_capsule_2x: string;
|
||||||
|
library_capsule: string;
|
||||||
|
library_capsule_2x: string;
|
||||||
|
library_hero: string;
|
||||||
|
library_hero_2x: string;
|
||||||
|
community_icon: string;
|
||||||
|
page_background_path: string;
|
||||||
|
raw_page_background: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PurchaseOption {
|
||||||
|
packageid?: number;
|
||||||
|
bundleid?: number;
|
||||||
|
purchase_option_name: string;
|
||||||
|
final_price_in_cents: string;
|
||||||
|
original_price_in_cents: string;
|
||||||
|
formatted_final_price: string;
|
||||||
|
formatted_original_price: string;
|
||||||
|
discount_pct: number;
|
||||||
|
active_discounts: ActiveDiscount[];
|
||||||
|
user_can_purchase_as_gift: boolean;
|
||||||
|
hide_discount_pct_for_compliance: boolean;
|
||||||
|
included_game_count: number;
|
||||||
|
bundle_discount_pct?: number;
|
||||||
|
price_before_bundle_discount?: string;
|
||||||
|
formatted_price_before_bundle_discount?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ActiveDiscount {
|
||||||
|
discount_amount: string;
|
||||||
|
discount_description: string;
|
||||||
|
discount_end_date: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Trailer {
|
||||||
|
trailer_name: string;
|
||||||
|
trailer_url_format: string;
|
||||||
|
trailer_category: number;
|
||||||
|
trailer_480p: TrailerFile[];
|
||||||
|
trailer_max: TrailerFile[];
|
||||||
|
microtrailer: TrailerFile[];
|
||||||
|
screenshot_medium: string;
|
||||||
|
screenshot_full: string;
|
||||||
|
trailer_base_id: number;
|
||||||
|
all_ages: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TrailerFile {
|
||||||
|
filename: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SupportedLanguage {
|
||||||
|
elanguage: number;
|
||||||
|
eadditionallanguage: number;
|
||||||
|
supported: boolean;
|
||||||
|
full_audio: boolean;
|
||||||
|
subtitles: boolean;
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import type {
|
|||||||
CompareResult,
|
CompareResult,
|
||||||
RankedShot,
|
RankedShot,
|
||||||
Shot,
|
Shot,
|
||||||
|
ProfileInfo,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import pLimit from 'p-limit';
|
import pLimit from 'p-limit';
|
||||||
@@ -18,6 +19,7 @@ import { LRUCache } from 'lru-cache';
|
|||||||
import sanitizeHtml from 'sanitize-html';
|
import sanitizeHtml from 'sanitize-html';
|
||||||
import { Agent as HttpAgent } from 'http';
|
import { Agent as HttpAgent } from 'http';
|
||||||
import { Agent as HttpsAgent } from 'https';
|
import { Agent as HttpsAgent } from 'https';
|
||||||
|
import { parseStringPromise } from "xml2js";
|
||||||
import sharp, { type Metadata } from 'sharp';
|
import sharp, { type Metadata } from 'sharp';
|
||||||
import AbortController from 'abort-controller';
|
import AbortController from 'abort-controller';
|
||||||
import fetch, { RequestInit } from 'node-fetch';
|
import fetch, { RequestInit } from 'node-fetch';
|
||||||
@@ -90,29 +92,21 @@ export namespace Utils {
|
|||||||
|
|
||||||
// --- Optimized Box Art creation ---
|
// --- Optimized Box Art creation ---
|
||||||
export async function createBoxArtBuffer(
|
export async function createBoxArtBuffer(
|
||||||
assets: LibraryAssetsFull,
|
logoUrl: string,
|
||||||
appid: number | string,
|
backgroundUrl: string,
|
||||||
logoPercent = 0.9
|
logoPercent = 0.9
|
||||||
): Promise<Buffer> {
|
): Promise<Buffer> {
|
||||||
const base = `https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/${appid}`;
|
|
||||||
const pick = (key: string) => {
|
|
||||||
const set = assets[key];
|
|
||||||
const path = set?.image2x?.english || set?.image?.english;
|
|
||||||
if (!path) throw new Error(`Missing asset for ${key}`);
|
|
||||||
return `${base}/${path}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const [bgBuf, logoBuf] = await Promise.all([
|
const [bgBuf, logoBuf] = await Promise.all([
|
||||||
downloadLimit(() =>
|
downloadLimit(() =>
|
||||||
fetchBuffer(pick('library_hero'))
|
fetchBuffer(backgroundUrl)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error(`Failed to download hero image for ${appid}:`, error);
|
console.error(`Failed to download hero image from ${backgroundUrl}:`, error);
|
||||||
throw new Error(`Failed to create box art: hero image unavailable`);
|
throw new Error(`Failed to create box art: hero image unavailable`);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
downloadLimit(() => fetchBuffer(pick('library_logo'))
|
downloadLimit(() => fetchBuffer(logoUrl)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error(`Failed to download logo image for ${appid}:`, error);
|
console.error(`Failed to download logo image from ${logoUrl}:`, error);
|
||||||
throw new Error(`Failed to create box art: logo image unavailable`);
|
throw new Error(`Failed to create box art: logo image unavailable`);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@@ -182,9 +176,11 @@ export namespace Utils {
|
|||||||
export function createSlug(name: string): string {
|
export function createSlug(name: string): string {
|
||||||
return name
|
return name
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.replace(/[^\w\s -]/g, "")
|
.normalize("NFKD") // Normalize to decompose accented characters
|
||||||
.replace(/\s+/g, "-")
|
.replace(/[^\p{L}\p{N}\s-]/gu, '') // Keep Unicode letters, numbers, spaces, and hyphens
|
||||||
.replace(/-+/g, "-")
|
.replace(/\s+/g, '-') // Replace spaces with hyphens
|
||||||
|
.replace(/-+/g, '-') // Collapse multiple hyphens
|
||||||
|
.replace(/^-+|-+$/g, '') // Trim leading/trailing hyphens
|
||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,16 +324,26 @@ export namespace Utils {
|
|||||||
export function compatibilityType(type?: string): "low" | "mid" | "high" | "unknown" {
|
export function compatibilityType(type?: string): "low" | "mid" | "high" | "unknown" {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "1":
|
case "1":
|
||||||
return "low";
|
return "high";
|
||||||
case "2":
|
case "2":
|
||||||
return "mid";
|
return "mid";
|
||||||
case "3":
|
case "3":
|
||||||
return "high";
|
return "low";
|
||||||
default:
|
default:
|
||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function estimateRatingFromSummary(
|
||||||
|
reviewCount: number,
|
||||||
|
percentPositive: number
|
||||||
|
): number {
|
||||||
|
const positiveVotes = Math.round((percentPositive / 100) * reviewCount);
|
||||||
|
const negativeVotes = reviewCount - positiveVotes;
|
||||||
|
return getRating(positiveVotes, negativeVotes);
|
||||||
|
}
|
||||||
|
|
||||||
export function mapGameTags<
|
export function mapGameTags<
|
||||||
T extends string = "tag"
|
T extends string = "tag"
|
||||||
>(
|
>(
|
||||||
@@ -353,6 +359,20 @@ export namespace Utils {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createType<
|
||||||
|
T extends "developer" | "publisher" | "franchise" | "tag" | "categorie" | "genre"
|
||||||
|
>(
|
||||||
|
names: string[],
|
||||||
|
type: T
|
||||||
|
) {
|
||||||
|
return names
|
||||||
|
.map(name => ({
|
||||||
|
type,
|
||||||
|
name: name.trim(),
|
||||||
|
slug: createSlug(name.trim())
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a tag object with name, slug, and type
|
* Create a tag object with name, slug, and type
|
||||||
* @typeparam T Literal type of the `type` field (defaults to 'tag')
|
* @typeparam T Literal type of the `type` field (defaults to 'tag')
|
||||||
@@ -380,17 +400,39 @@ export namespace Utils {
|
|||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDepotEntry(e: any): e is DepotEntry {
|
||||||
|
return (
|
||||||
|
e != null &&
|
||||||
|
typeof e === 'object' &&
|
||||||
|
'manifests' in e &&
|
||||||
|
e.manifests != null &&
|
||||||
|
typeof e.manifests.public?.download === 'string'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function getPublicDepotSizes(depots: AppDepots) {
|
export function getPublicDepotSizes(depots: AppDepots) {
|
||||||
const sum = { download: 0, size: 0 };
|
let download = 0;
|
||||||
for (const key in depots) {
|
let size = 0;
|
||||||
|
|
||||||
|
for (const key of Object.keys(depots)) {
|
||||||
if (key === 'branches' || key === 'privatebranches') continue;
|
if (key === 'branches' || key === 'privatebranches') continue;
|
||||||
const entry = depots[key] as DepotEntry;
|
const entry = depots[key] as DepotEntry;
|
||||||
if ('manifests' in entry && entry.manifests.public) {
|
if (!isDepotEntry(entry)) {
|
||||||
sum.download += Number(entry.manifests.public.download);
|
continue;
|
||||||
sum.size += Number(entry.manifests.public.size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dl = Number(entry.manifests.public.download);
|
||||||
|
const sz = Number(entry.manifests.public.size);
|
||||||
|
if (!Number.isFinite(dl) || !Number.isFinite(sz)) {
|
||||||
|
console.warn(`[getPublicDepotSizes] non-numeric size for depot ${key}`);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
return { downloadSize: sum.download, sizeOnDisk: sum.size };
|
|
||||||
|
download += dl;
|
||||||
|
size += sz;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { downloadSize: download, sizeOnDisk: size };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseGenres(str: string): GenreType[] {
|
export function parseGenres(str: string): GenreType[] {
|
||||||
@@ -419,4 +461,64 @@ export namespace Utils {
|
|||||||
|
|
||||||
return cleaned.trim()
|
return cleaned.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches and parses a single Steam community profile XML.
|
||||||
|
* @param steamIdOrVanity - The 64-bit SteamID or vanity name.
|
||||||
|
* @returns Promise resolving to ProfileInfo.
|
||||||
|
*/
|
||||||
|
export async function fetchProfileInfo(
|
||||||
|
steamIdOrVanity: string
|
||||||
|
): Promise<ProfileInfo> {
|
||||||
|
const isNumericId = /^\d+$/.test(steamIdOrVanity);
|
||||||
|
const path = isNumericId ? `profiles/${steamIdOrVanity}` : `id/${steamIdOrVanity}`;
|
||||||
|
const url = `https://steamcommunity.com/${path}/?xml=1`;
|
||||||
|
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch ${steamIdOrVanity}: HTTP ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const xml = await response.text();
|
||||||
|
const { profile } = await parseStringPromise(xml, {
|
||||||
|
explicitArray: false,
|
||||||
|
trim: true,
|
||||||
|
mergeAttrs: true
|
||||||
|
}) as { profile: any };
|
||||||
|
|
||||||
|
// Extract fields (fall back to limitedAccount tag if needed)
|
||||||
|
const limitedFlag = profile.isLimitedAccount ?? profile.limitedAccount;
|
||||||
|
const isLimited = limitedFlag === '1';
|
||||||
|
|
||||||
|
return {
|
||||||
|
isLimited,
|
||||||
|
steamID64: profile.steamID64,
|
||||||
|
privacyState: profile.privacyState,
|
||||||
|
visibility: profile.visibilityState
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Batch-fetches multiple Steam profiles in parallel.
|
||||||
|
* @param idsOrVanities - Array of SteamID64 strings or vanity names.
|
||||||
|
* @returns Promise resolving to a record mapping each input to its ProfileInfo or an error.
|
||||||
|
*/
|
||||||
|
export async function fetchProfilesInfo(
|
||||||
|
idsOrVanities: string[]
|
||||||
|
): Promise<Map<string, ProfileInfo | { error: string }>> {
|
||||||
|
const results = await Promise.all(
|
||||||
|
idsOrVanities.map(async (input) => {
|
||||||
|
try {
|
||||||
|
const info = await fetchProfileInfo(input);
|
||||||
|
return { input, result: info };
|
||||||
|
} catch (err) {
|
||||||
|
return { input, result: { error: (err as Error).message } };
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return new Map(
|
||||||
|
results.map(({ input, result }) => [input, result] as [string, ProfileInfo | { error: string }])
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { sql } from "drizzle-orm";
|
|
||||||
import "zod-openapi/extend";
|
import "zod-openapi/extend";
|
||||||
|
import { sql } from "drizzle-orm";
|
||||||
|
|
||||||
export namespace Common {
|
export namespace Common {
|
||||||
export const IdDescription = `Unique object identifier.
|
export const IdDescription = `Unique object identifier.
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import { steamTable } from "../steam/steam.sql";
|
|
||||||
import { pgTable, primaryKey, varchar } from "drizzle-orm/pg-core";
|
|
||||||
import { encryptedText, ulid, timestamps, utc } from "../drizzle/types";
|
|
||||||
|
|
||||||
export const steamCredentialsTable = pgTable(
|
|
||||||
"steam_account_credentials",
|
|
||||||
{
|
|
||||||
...timestamps,
|
|
||||||
id: ulid("id").notNull(),
|
|
||||||
steamID: varchar("steam_id", { length: 255 })
|
|
||||||
.notNull()
|
|
||||||
.references(() => steamTable.id, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
}),
|
|
||||||
refreshToken: encryptedText("refresh_token")
|
|
||||||
.notNull(),
|
|
||||||
expiry: utc("expiry").notNull(),
|
|
||||||
username: varchar("username", { length: 255 }).notNull(),
|
|
||||||
},
|
|
||||||
(table) => [
|
|
||||||
primaryKey({
|
|
||||||
columns: [table.steamID, table.id]
|
|
||||||
})
|
|
||||||
]
|
|
||||||
)
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
import { Resource } from "sst";
|
|
||||||
import { bus } from "sst/aws/bus";
|
|
||||||
import { createEvent } from "../event";
|
|
||||||
import { createID, fn } from "../utils";
|
|
||||||
import { eq, and, isNull, gt } from "drizzle-orm";
|
|
||||||
import { createSelectSchema } from "drizzle-zod";
|
|
||||||
import { steamCredentialsTable } from "./credentials.sql";
|
|
||||||
import { afterTx, createTransaction, useTransaction } from "../drizzle/transaction";
|
|
||||||
|
|
||||||
export namespace Credentials {
|
|
||||||
export const Info = createSelectSchema(steamCredentialsTable)
|
|
||||||
.omit({ timeCreated: true, timeDeleted: true, timeUpdated: true })
|
|
||||||
.extend({
|
|
||||||
accessToken: z.string(),
|
|
||||||
cookies: z.string().array()
|
|
||||||
})
|
|
||||||
|
|
||||||
export type Info = z.infer<typeof Info>;
|
|
||||||
|
|
||||||
export const Events = {
|
|
||||||
New: createEvent(
|
|
||||||
"new_credentials.added",
|
|
||||||
z.object({
|
|
||||||
steamID: Info.shape.steamID,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const create = fn(
|
|
||||||
Info
|
|
||||||
.omit({ accessToken: true, cookies: true, expiry: true })
|
|
||||||
.partial({ id: true }),
|
|
||||||
(input) => {
|
|
||||||
const part = input.refreshToken.split('.')[1] as string
|
|
||||||
|
|
||||||
const payload = JSON.parse(Buffer.from(part, 'base64').toString());
|
|
||||||
|
|
||||||
return createTransaction(async (tx) => {
|
|
||||||
const id = input.id ?? createID("credentials")
|
|
||||||
await tx
|
|
||||||
.insert(steamCredentialsTable)
|
|
||||||
.values({
|
|
||||||
id,
|
|
||||||
steamID: input.steamID,
|
|
||||||
username: input.username,
|
|
||||||
refreshToken: input.refreshToken,
|
|
||||||
expiry: new Date(payload.exp * 1000),
|
|
||||||
})
|
|
||||||
await afterTx(async () =>
|
|
||||||
await bus.publish(Resource.Bus, Events.New, { steamID: input.steamID })
|
|
||||||
);
|
|
||||||
return id
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fromSteamID = fn(
|
|
||||||
Info.shape.steamID,
|
|
||||||
(steamID) =>
|
|
||||||
useTransaction(async (tx) => {
|
|
||||||
const now = new Date()
|
|
||||||
|
|
||||||
const credential = await tx
|
|
||||||
.select()
|
|
||||||
.from(steamCredentialsTable)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(steamCredentialsTable.steamID, steamID),
|
|
||||||
isNull(steamCredentialsTable.timeDeleted),
|
|
||||||
gt(steamCredentialsTable.expiry, now)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.execute()
|
|
||||||
.then(rows => rows.at(0));
|
|
||||||
|
|
||||||
if (!credential) return null;
|
|
||||||
|
|
||||||
return serialize(credential);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
export function serialize(
|
|
||||||
input: typeof steamCredentialsTable.$inferSelect,
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
id: input.id,
|
|
||||||
expiry: input.expiry,
|
|
||||||
steamID: input.steamID,
|
|
||||||
username: input.username,
|
|
||||||
refreshToken: input.refreshToken,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Token } from "../utils";
|
import { char, timestamp as rawTs } from "drizzle-orm/pg-core";
|
||||||
import { char, customType, timestamp as rawTs } from "drizzle-orm/pg-core";
|
|
||||||
|
|
||||||
export const ulid = (name: string) => char(name, { length: 26 + 4 });
|
export const ulid = (name: string) => char(name, { length: 26 + 4 });
|
||||||
|
|
||||||
@@ -33,19 +32,6 @@ export const utc = (name: string) =>
|
|||||||
// mode: "date"
|
// mode: "date"
|
||||||
});
|
});
|
||||||
|
|
||||||
export const encryptedText =
|
|
||||||
customType<{ data: string; driverData: string; }>({
|
|
||||||
dataType() {
|
|
||||||
return 'text';
|
|
||||||
},
|
|
||||||
fromDriver(val) {
|
|
||||||
return Token.decrypt(val);
|
|
||||||
},
|
|
||||||
toDriver(val) {
|
|
||||||
return Token.encrypt(val);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const timestamps = {
|
export const timestamps = {
|
||||||
timeCreated: utc("time_created").notNull().defaultNow(),
|
timeCreated: utc("time_created").notNull().defaultNow(),
|
||||||
timeUpdated: utc("time_updated").notNull().defaultNow(),
|
timeUpdated: utc("time_updated").notNull().defaultNow(),
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export namespace Examples {
|
|||||||
|
|
||||||
export const SteamAccount = {
|
export const SteamAccount = {
|
||||||
status: "online" as const, //offline,dnd(do not disturb) or playing
|
status: "online" as const, //offline,dnd(do not disturb) or playing
|
||||||
id: "74839300282033",// Primary key
|
id: "74839300282033",// Steam ID
|
||||||
userID: User.id,// | null FK to User (null if not linked)
|
userID: User.id,// | null FK to User (null if not linked)
|
||||||
name: "JD The 65th",
|
name: "JD The 65th",
|
||||||
username: "jdoe",
|
username: "jdoe",
|
||||||
@@ -55,11 +55,10 @@ export namespace Examples {
|
|||||||
|
|
||||||
export const Team = {
|
export const Team = {
|
||||||
id: Id("team"),// Primary key
|
id: Id("team"),// Primary key
|
||||||
name: "John's Console", // Team name (not null, unique)
|
name: "John", // Team name (not null, unique)
|
||||||
ownerID: User.id, // FK to User who owns/created the team
|
|
||||||
slug: SteamAccount.profileUrl.toLowerCase(),
|
|
||||||
maxMembers: 3,
|
maxMembers: 3,
|
||||||
inviteCode: "xwydjf",
|
inviteCode: "xwydjf",
|
||||||
|
ownerSteamID: SteamAccount.id, // FK to User who owns/created the team
|
||||||
members: [SteamAccount]
|
members: [SteamAccount]
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -152,6 +151,9 @@ export namespace Examples {
|
|||||||
id: "1809540",
|
id: "1809540",
|
||||||
slug: "nine-sols",
|
slug: "nine-sols",
|
||||||
name: "Nine Sols",
|
name: "Nine Sols",
|
||||||
|
links:[
|
||||||
|
"https://example.com"
|
||||||
|
],
|
||||||
controllerSupport: "full" as const,
|
controllerSupport: "full" as const,
|
||||||
releaseDate: new Date("2024-05-29T06:53:24.000Z"),
|
releaseDate: new Date("2024-05-29T06:53:24.000Z"),
|
||||||
compatibility: "high" as const,
|
compatibility: "high" as const,
|
||||||
@@ -205,6 +207,13 @@ export namespace Examples {
|
|||||||
slug: "redcandlegames"
|
slug: "redcandlegames"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
franchises: [],
|
||||||
|
categories: [
|
||||||
|
{
|
||||||
|
name: "Partial Controller",
|
||||||
|
slug: "partial-controller"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CommonImg = [
|
export const CommonImg = [
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import { friendTable } from "./friend.sql";
|
|||||||
import { userTable } from "../user/user.sql";
|
import { userTable } from "../user/user.sql";
|
||||||
import { steamTable } from "../steam/steam.sql";
|
import { steamTable } from "../steam/steam.sql";
|
||||||
import { createSelectSchema } from "drizzle-zod";
|
import { createSelectSchema } from "drizzle-zod";
|
||||||
|
import { and, eq, isNull, sql } from "drizzle-orm";
|
||||||
import { groupBy, map, pipe, values } from "remeda";
|
import { groupBy, map, pipe, values } from "remeda";
|
||||||
import { ErrorCodes, VisibleError } from "../error";
|
import { ErrorCodes, VisibleError } from "../error";
|
||||||
import { and, eq, isNull, sql } from "drizzle-orm";
|
|
||||||
import { createTransaction, useTransaction } from "../drizzle/transaction";
|
import { createTransaction, useTransaction } from "../drizzle/transaction";
|
||||||
|
|
||||||
export namespace Friend {
|
export namespace Friend {
|
||||||
|
|||||||
@@ -19,24 +19,21 @@ export namespace Library {
|
|||||||
export type Info = z.infer<typeof Info>;
|
export type Info = z.infer<typeof Info>;
|
||||||
|
|
||||||
export const Events = {
|
export const Events = {
|
||||||
Queue: createEvent(
|
Add: createEvent(
|
||||||
"library.queue",
|
"library.add",
|
||||||
z.object({
|
z.object({
|
||||||
appID: z.number(),
|
appID: z.number(),
|
||||||
lastPlayed: z.date(),
|
lastPlayed: z.date().nullable(),
|
||||||
timeAcquired: z.date(),
|
|
||||||
totalPlaytime: z.number(),
|
totalPlaytime: z.number(),
|
||||||
isFamilyShared: z.boolean(),
|
}),
|
||||||
isFamilyShareable: z.boolean(),
|
|
||||||
}).array(),
|
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const add = fn(
|
export const add = fn(
|
||||||
Info.partial({ ownerID: true }),
|
Info.partial({ ownerSteamID: true }),
|
||||||
async (input) =>
|
async (input) =>
|
||||||
createTransaction(async (tx) => {
|
createTransaction(async (tx) => {
|
||||||
const ownerSteamID = input.ownerID ?? Actor.steamID()
|
const ownerSteamID = input.ownerSteamID ?? Actor.steamID()
|
||||||
const result =
|
const result =
|
||||||
await tx
|
await tx
|
||||||
.select()
|
.select()
|
||||||
@@ -44,7 +41,7 @@ export namespace Library {
|
|||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(steamLibraryTable.baseGameID, input.baseGameID),
|
eq(steamLibraryTable.baseGameID, input.baseGameID),
|
||||||
eq(steamLibraryTable.ownerID, ownerSteamID),
|
eq(steamLibraryTable.ownerSteamID, ownerSteamID),
|
||||||
isNull(steamLibraryTable.timeDeleted)
|
isNull(steamLibraryTable.timeDeleted)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -57,21 +54,17 @@ export namespace Library {
|
|||||||
await tx
|
await tx
|
||||||
.insert(steamLibraryTable)
|
.insert(steamLibraryTable)
|
||||||
.values({
|
.values({
|
||||||
ownerID: ownerSteamID,
|
ownerSteamID: ownerSteamID,
|
||||||
baseGameID: input.baseGameID,
|
baseGameID: input.baseGameID,
|
||||||
lastPlayed: input.lastPlayed,
|
lastPlayed: input.lastPlayed,
|
||||||
totalPlaytime: input.totalPlaytime,
|
totalPlaytime: input.totalPlaytime,
|
||||||
timeAcquired: input.timeAcquired,
|
|
||||||
isFamilyShared: input.isFamilyShared
|
|
||||||
})
|
})
|
||||||
.onConflictDoUpdate({
|
.onConflictDoUpdate({
|
||||||
target: [steamLibraryTable.ownerID, steamLibraryTable.baseGameID],
|
target: [steamLibraryTable.ownerSteamID, steamLibraryTable.baseGameID],
|
||||||
set: {
|
set: {
|
||||||
timeDeleted: null,
|
timeDeleted: null,
|
||||||
lastPlayed: input.lastPlayed,
|
lastPlayed: input.lastPlayed,
|
||||||
timeAcquired: input.timeAcquired,
|
|
||||||
totalPlaytime: input.totalPlaytime,
|
totalPlaytime: input.totalPlaytime,
|
||||||
isFamilyShared: input.isFamilyShared
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -87,7 +80,7 @@ export namespace Library {
|
|||||||
.set({ timeDeleted: sql`now()` })
|
.set({ timeDeleted: sql`now()` })
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(steamLibraryTable.ownerID, input.ownerID),
|
eq(steamLibraryTable.ownerSteamID, input.ownerSteamID),
|
||||||
eq(steamLibraryTable.baseGameID, input.baseGameID),
|
eq(steamLibraryTable.baseGameID, input.baseGameID),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -105,7 +98,7 @@ export namespace Library {
|
|||||||
.from(steamLibraryTable)
|
.from(steamLibraryTable)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(steamLibraryTable.ownerID, Actor.steamID()),
|
eq(steamLibraryTable.ownerSteamID, Actor.steamID()),
|
||||||
isNull(steamLibraryTable.timeDeleted)
|
isNull(steamLibraryTable.timeDeleted)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { timestamps, utc, } from "../drizzle/types";
|
|
||||||
import { steamTable } from "../steam/steam.sql";
|
import { steamTable } from "../steam/steam.sql";
|
||||||
|
import { timestamps, utc, } from "../drizzle/types";
|
||||||
import { baseGamesTable } from "../base-game/base-game.sql";
|
import { baseGamesTable } from "../base-game/base-game.sql";
|
||||||
import { boolean, index, integer, pgTable, primaryKey, varchar, } from "drizzle-orm/pg-core";
|
import { index, integer, pgTable, primaryKey, varchar, } from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
export const steamLibraryTable = pgTable(
|
export const steamLibraryTable = pgTable(
|
||||||
"game_libraries",
|
"game_libraries",
|
||||||
@@ -12,20 +12,18 @@ export const steamLibraryTable = pgTable(
|
|||||||
.references(() => baseGamesTable.id, {
|
.references(() => baseGamesTable.id, {
|
||||||
onDelete: "cascade"
|
onDelete: "cascade"
|
||||||
}),
|
}),
|
||||||
ownerID: varchar("owner_id", { length: 255 })
|
ownerSteamID: varchar("owner_steam_id", { length: 255 })
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => steamTable.id, {
|
.references(() => steamTable.id, {
|
||||||
onDelete: "cascade"
|
onDelete: "cascade"
|
||||||
}),
|
}),
|
||||||
timeAcquired: utc("time_acquired").notNull(),
|
lastPlayed: utc("last_played"),
|
||||||
lastPlayed: utc("last_played").notNull(),
|
|
||||||
totalPlaytime: integer("total_playtime").notNull(),
|
totalPlaytime: integer("total_playtime").notNull(),
|
||||||
isFamilyShared: boolean("is_family_shared").notNull()
|
|
||||||
},
|
},
|
||||||
(table) => [
|
(table) => [
|
||||||
primaryKey({
|
primaryKey({
|
||||||
columns: [table.baseGameID, table.ownerID]
|
columns: [table.baseGameID, table.ownerSteamID]
|
||||||
}),
|
}),
|
||||||
index("idx_game_libraries_owner_id").on(table.ownerID),
|
index("idx_game_libraries_owner_id").on(table.ownerSteamID),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
import { Actor } from "../actor";
|
|
||||||
import { Common } from "../common";
|
|
||||||
import { Examples } from "../examples";
|
|
||||||
import { createID, fn } from "../utils";
|
|
||||||
import { and, eq, isNull } from "drizzle-orm"
|
|
||||||
import { memberTable, RoleEnum } from "./member.sql";
|
|
||||||
import { createTransaction, useTransaction } from "../drizzle/transaction";
|
|
||||||
|
|
||||||
export namespace Member {
|
|
||||||
export const Info = z
|
|
||||||
.object({
|
|
||||||
id: z.string().openapi({
|
|
||||||
description: Common.IdDescription,
|
|
||||||
example: Examples.Member.id,
|
|
||||||
}),
|
|
||||||
teamID: z.string().openapi({
|
|
||||||
description: "Associated team identifier for this membership",
|
|
||||||
example: Examples.Member.teamID
|
|
||||||
}),
|
|
||||||
role: z.enum(RoleEnum.enumValues).openapi({
|
|
||||||
description: "Assigned permission role within the team",
|
|
||||||
example: Examples.Member.role
|
|
||||||
}),
|
|
||||||
steamID: z.string().openapi({
|
|
||||||
description: "Steam platform identifier for Steam account integration",
|
|
||||||
example: Examples.Member.steamID
|
|
||||||
}),
|
|
||||||
userID: z.string().nullable().openapi({
|
|
||||||
description: "Optional associated user account identifier",
|
|
||||||
example: Examples.Member.userID
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.openapi({
|
|
||||||
ref: "Member",
|
|
||||||
description: "Team membership entity defining user roles and platform connections",
|
|
||||||
example: Examples.Member,
|
|
||||||
});
|
|
||||||
|
|
||||||
export type Info = z.infer<typeof Info>;
|
|
||||||
|
|
||||||
export const create = fn(
|
|
||||||
Info
|
|
||||||
.partial({
|
|
||||||
id: true,
|
|
||||||
userID: true,
|
|
||||||
teamID: true
|
|
||||||
}),
|
|
||||||
(input) =>
|
|
||||||
createTransaction(async (tx) => {
|
|
||||||
const id = input.id ?? createID("member");
|
|
||||||
await tx.insert(memberTable).values({
|
|
||||||
id,
|
|
||||||
role: input.role,
|
|
||||||
userID: input.userID,
|
|
||||||
steamID: input.steamID,
|
|
||||||
teamID: input.teamID ?? Actor.teamID(),
|
|
||||||
})
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const fromTeamID = fn(
|
|
||||||
Info.shape.teamID,
|
|
||||||
(teamID) =>
|
|
||||||
useTransaction((tx) =>
|
|
||||||
tx
|
|
||||||
.select()
|
|
||||||
.from(memberTable)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(memberTable.userID, Actor.userID()),
|
|
||||||
eq(memberTable.teamID, teamID),
|
|
||||||
isNull(memberTable.timeDeleted)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.execute()
|
|
||||||
.then(rows => rows.map(serialize).at(0))
|
|
||||||
)
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
export const fromUserID = fn(
|
|
||||||
z.string(),
|
|
||||||
(userID) =>
|
|
||||||
useTransaction((tx) =>
|
|
||||||
tx
|
|
||||||
.select()
|
|
||||||
.from(memberTable)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(memberTable.userID, userID),
|
|
||||||
eq(memberTable.teamID, Actor.teamID()),
|
|
||||||
isNull(memberTable.timeDeleted)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.execute()
|
|
||||||
.then(rows => rows.map(serialize).at(0))
|
|
||||||
)
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a raw member database row into a standardized {@link Member.Info} object.
|
|
||||||
*
|
|
||||||
* @param input - The database row representing a member.
|
|
||||||
* @returns The member information formatted as a {@link Member.Info} object.
|
|
||||||
*/
|
|
||||||
export function serialize(
|
|
||||||
input: typeof memberTable.$inferSelect,
|
|
||||||
): z.infer<typeof Info> {
|
|
||||||
return {
|
|
||||||
id: input.id,
|
|
||||||
role: input.role,
|
|
||||||
userID: input.userID,
|
|
||||||
teamID: input.teamID,
|
|
||||||
steamID: input.steamID
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import { isNotNull } from "drizzle-orm";
|
|
||||||
import { userTable } from "../user/user.sql";
|
|
||||||
import { steamTable } from "../steam/steam.sql";
|
|
||||||
import { timestamps, teamID, ulid } from "../drizzle/types";
|
|
||||||
import { bigint, pgEnum, pgTable, primaryKey, uniqueIndex, varchar } from "drizzle-orm/pg-core";
|
|
||||||
|
|
||||||
export const RoleEnum = pgEnum("member_role", ["child", "adult"])
|
|
||||||
|
|
||||||
export const memberTable = pgTable(
|
|
||||||
"members",
|
|
||||||
{
|
|
||||||
...teamID,
|
|
||||||
...timestamps,
|
|
||||||
userID: ulid("user_id")
|
|
||||||
.references(() => userTable.id, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
}),
|
|
||||||
steamID: varchar("steam_id", { length: 255 })
|
|
||||||
.notNull()
|
|
||||||
.references(() => steamTable.id, {
|
|
||||||
onDelete: "cascade",
|
|
||||||
onUpdate: "restrict"
|
|
||||||
}),
|
|
||||||
role: RoleEnum("role").notNull(),
|
|
||||||
},
|
|
||||||
(table) => [
|
|
||||||
primaryKey({ columns: [table.id, table.teamID] }),
|
|
||||||
uniqueIndex("idx_member_steam_id").on(table.teamID, table.steamID),
|
|
||||||
uniqueIndex("idx_member_user_id")
|
|
||||||
.on(table.teamID, table.userID)
|
|
||||||
.where(isNotNull(table.userID))
|
|
||||||
],
|
|
||||||
);
|
|
||||||
@@ -4,12 +4,11 @@ import { Resource } from "sst";
|
|||||||
import { Actor } from "../actor";
|
import { Actor } from "../actor";
|
||||||
import { bus } from "sst/aws/bus";
|
import { bus } from "sst/aws/bus";
|
||||||
import { Common } from "../common";
|
import { Common } from "../common";
|
||||||
import { createEvent } from "../event";
|
|
||||||
import { Examples } from "../examples";
|
import { Examples } from "../examples";
|
||||||
|
import { createEvent } from "../event";
|
||||||
import { eq, and, isNull, desc } from "drizzle-orm";
|
import { eq, and, isNull, desc } from "drizzle-orm";
|
||||||
import { steamTable, StatusEnum, Limitations } from "./steam.sql";
|
import { steamTable, StatusEnum, Limitations } from "./steam.sql";
|
||||||
import { afterTx, createTransaction, useTransaction } from "../drizzle/transaction";
|
import { afterTx, createTransaction, useTransaction } from "../drizzle/transaction";
|
||||||
import { teamTable } from "../team/team.sql";
|
|
||||||
|
|
||||||
export namespace Steam {
|
export namespace Steam {
|
||||||
export const Info = z
|
export const Info = z
|
||||||
@@ -34,14 +33,6 @@ export namespace Steam {
|
|||||||
description: "The steam community url of this account",
|
description: "The steam community url of this account",
|
||||||
example: Examples.SteamAccount.profileUrl
|
example: Examples.SteamAccount.profileUrl
|
||||||
}),
|
}),
|
||||||
username: z.string()
|
|
||||||
.regex(/^[a-z0-9]{1,32}$/, "The Steam username is not slug friendly")
|
|
||||||
.nullable()
|
|
||||||
.openapi({
|
|
||||||
description: "The unique username of this account",
|
|
||||||
example: Examples.SteamAccount.username
|
|
||||||
})
|
|
||||||
.default("unknown"),
|
|
||||||
realName: z.string().nullable().openapi({
|
realName: z.string().nullable().openapi({
|
||||||
description: "The real name behind of this Steam account",
|
description: "The real name behind of this Steam account",
|
||||||
example: Examples.SteamAccount.realName
|
example: Examples.SteamAccount.realName
|
||||||
@@ -76,7 +67,7 @@ export namespace Steam {
|
|||||||
"steam_account.created",
|
"steam_account.created",
|
||||||
z.object({
|
z.object({
|
||||||
steamID: Info.shape.id,
|
steamID: Info.shape.id,
|
||||||
userID: Info.shape.userID
|
userID: Info.shape.userID,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
Updated: createEvent(
|
Updated: createEvent(
|
||||||
@@ -94,9 +85,9 @@ export namespace Steam {
|
|||||||
useUser: z.boolean(),
|
useUser: z.boolean(),
|
||||||
})
|
})
|
||||||
.partial({
|
.partial({
|
||||||
useUser: true,
|
|
||||||
userID: true,
|
userID: true,
|
||||||
status: true,
|
status: true,
|
||||||
|
useUser: true,
|
||||||
lastSyncedAt: true
|
lastSyncedAt: true
|
||||||
}),
|
}),
|
||||||
(input) =>
|
(input) =>
|
||||||
@@ -107,8 +98,8 @@ export namespace Steam {
|
|||||||
.from(steamTable)
|
.from(steamTable)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(steamTable.id, input.id),
|
isNull(steamTable.timeDeleted),
|
||||||
isNull(steamTable.timeDeleted)
|
eq(steamTable.id, input.id)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.execute()
|
.execute()
|
||||||
@@ -129,14 +120,13 @@ export namespace Steam {
|
|||||||
avatarHash: input.avatarHash,
|
avatarHash: input.avatarHash,
|
||||||
limitations: input.limitations,
|
limitations: input.limitations,
|
||||||
status: input.status ?? "offline",
|
status: input.status ?? "offline",
|
||||||
username: input.username ?? "unknown",
|
|
||||||
steamMemberSince: input.steamMemberSince,
|
steamMemberSince: input.steamMemberSince,
|
||||||
lastSyncedAt: input.lastSyncedAt ?? Common.utc(),
|
lastSyncedAt: input.lastSyncedAt ?? Common.utc(),
|
||||||
})
|
})
|
||||||
|
|
||||||
// await afterTx(async () =>
|
await afterTx(async () =>
|
||||||
// bus.publish(Resource.Bus, Events.Created, { userID, steamID: input.id })
|
bus.publish(Resource.Bus, Events.Created, { userID, steamID: input.id })
|
||||||
// );
|
);
|
||||||
|
|
||||||
return input.id
|
return input.id
|
||||||
}),
|
}),
|
||||||
@@ -151,8 +141,8 @@ export namespace Steam {
|
|||||||
.partial({
|
.partial({
|
||||||
userID: true
|
userID: true
|
||||||
}),
|
}),
|
||||||
(input) =>
|
async (input) =>
|
||||||
useTransaction(async (tx) => {
|
createTransaction(async (tx) => {
|
||||||
const userID = input.userID ?? Actor.userID()
|
const userID = input.userID ?? Actor.userID()
|
||||||
await tx
|
await tx
|
||||||
.update(steamTable)
|
.update(steamTable)
|
||||||
@@ -160,6 +150,12 @@ export namespace Steam {
|
|||||||
userID
|
userID
|
||||||
})
|
})
|
||||||
.where(eq(steamTable.id, input.steamID));
|
.where(eq(steamTable.id, input.steamID));
|
||||||
|
|
||||||
|
await afterTx(async () =>
|
||||||
|
bus.publish(Resource.Bus, Events.Updated, { userID, steamID: input.steamID })
|
||||||
|
);
|
||||||
|
|
||||||
|
return input.steamID
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -177,6 +173,26 @@ export namespace Steam {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const confirmOwnerShip = fn(
|
||||||
|
z.string().min(1),
|
||||||
|
(userID) =>
|
||||||
|
useTransaction((tx) =>
|
||||||
|
tx
|
||||||
|
.select()
|
||||||
|
.from(steamTable)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(steamTable.userID, userID),
|
||||||
|
eq(steamTable.id, Actor.steamID()),
|
||||||
|
isNull(steamTable.timeDeleted)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.orderBy(desc(steamTable.timeCreated))
|
||||||
|
.execute()
|
||||||
|
.then((rows) => rows.map(serialize).at(0))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
export const fromSteamID = fn(
|
export const fromSteamID = fn(
|
||||||
z.string(),
|
z.string(),
|
||||||
(steamID) =>
|
(steamID) =>
|
||||||
@@ -208,15 +224,14 @@ export namespace Steam {
|
|||||||
return {
|
return {
|
||||||
id: input.id,
|
id: input.id,
|
||||||
name: input.name,
|
name: input.name,
|
||||||
userID: input.userID,
|
|
||||||
status: input.status,
|
status: input.status,
|
||||||
username: input.username,
|
userID: input.userID,
|
||||||
realName: input.realName,
|
realName: input.realName,
|
||||||
|
profileUrl: input.profileUrl,
|
||||||
avatarHash: input.avatarHash,
|
avatarHash: input.avatarHash,
|
||||||
limitations: input.limitations,
|
limitations: input.limitations,
|
||||||
lastSyncedAt: input.lastSyncedAt,
|
lastSyncedAt: input.lastSyncedAt,
|
||||||
steamMemberSince: input.steamMemberSince,
|
steamMemberSince: input.steamMemberSince,
|
||||||
profileUrl: input.profileUrl ? `https://steamcommunity.com/id/${input.profileUrl}` : null,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { userTable } from "../user/user.sql";
|
import { userTable } from "../user/user.sql";
|
||||||
import { timestamps, ulid, utc } from "../drizzle/types";
|
import { id, timestamps, ulid, utc } from "../drizzle/types";
|
||||||
import { pgTable, varchar, pgEnum, json, unique } from "drizzle-orm/pg-core";
|
import { pgTable, varchar, pgEnum, json, unique } from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
export const StatusEnum = pgEnum("steam_status", ["online", "offline", "dnd", "playing"])
|
export const StatusEnum = pgEnum("steam_status", ["online", "offline", "dnd", "playing"])
|
||||||
@@ -32,11 +32,7 @@ export const steamTable = pgTable(
|
|||||||
steamMemberSince: utc("member_since").notNull(),
|
steamMemberSince: utc("member_since").notNull(),
|
||||||
name: varchar("name", { length: 255 }).notNull(),
|
name: varchar("name", { length: 255 }).notNull(),
|
||||||
profileUrl: varchar("profile_url", { length: 255 }),
|
profileUrl: varchar("profile_url", { length: 255 }),
|
||||||
username: varchar("username", { length: 255 }).notNull(),
|
|
||||||
avatarHash: varchar("avatar_hash", { length: 255 }).notNull(),
|
avatarHash: varchar("avatar_hash", { length: 255 }).notNull(),
|
||||||
limitations: json("limitations").$type<Limitations>().notNull(),
|
limitations: json("limitations").$type<Limitations>().notNull(),
|
||||||
},
|
}
|
||||||
(table) => [
|
|
||||||
unique("idx_steam_username").on(table.username)
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
import { Steam } from "../steam";
|
|
||||||
import { Actor } from "../actor";
|
|
||||||
import { Common } from "../common";
|
|
||||||
import { teamTable } from "./team.sql";
|
|
||||||
import { Examples } from "../examples";
|
|
||||||
import { and, eq, isNull } from "drizzle-orm";
|
|
||||||
import { steamTable } from "../steam/steam.sql";
|
|
||||||
import { createID, fn, Invite } from "../utils";
|
|
||||||
import { memberTable } from "../member/member.sql";
|
|
||||||
import { groupBy, pipe, values, map } from "remeda";
|
|
||||||
import { createTransaction, useTransaction, type Transaction } from "../drizzle/transaction";
|
|
||||||
|
|
||||||
export namespace Team {
|
|
||||||
export const Info = z
|
|
||||||
.object({
|
|
||||||
id: z.string().openapi({
|
|
||||||
description: Common.IdDescription,
|
|
||||||
example: Examples.Team.id,
|
|
||||||
}),
|
|
||||||
slug: z.string().regex(/^[a-z0-9-]{1,32}$/, "Use a URL friendly name.").openapi({
|
|
||||||
description: "URL-friendly unique username (lowercase alphanumeric with hyphens)",
|
|
||||||
example: Examples.Team.slug
|
|
||||||
}),
|
|
||||||
name: z.string().openapi({
|
|
||||||
description: "Display name of the team",
|
|
||||||
example: Examples.Team.name
|
|
||||||
}),
|
|
||||||
ownerID: z.string().openapi({
|
|
||||||
description: "Unique identifier of the team owner",
|
|
||||||
example: Examples.Team.ownerID
|
|
||||||
}),
|
|
||||||
maxMembers: z.number().openapi({
|
|
||||||
description: "Maximum allowed team members based on subscription tier",
|
|
||||||
example: Examples.Team.maxMembers
|
|
||||||
}),
|
|
||||||
inviteCode: z.string().openapi({
|
|
||||||
description: "Unique invitation code used for adding new team members",
|
|
||||||
example: Examples.Team.inviteCode
|
|
||||||
}),
|
|
||||||
members: Steam.Info.array().openapi({
|
|
||||||
description: "All the team members in this team",
|
|
||||||
example: Examples.Team.members
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.openapi({
|
|
||||||
ref: "Team",
|
|
||||||
description: "Team entity containing core team information and settings",
|
|
||||||
example: Examples.Team,
|
|
||||||
});
|
|
||||||
|
|
||||||
export type Info = z.infer<typeof Info>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a unique team invite code
|
|
||||||
* @param length The length of the invite code
|
|
||||||
* @param maxAttempts Maximum number of attempts to generate a unique code
|
|
||||||
* @returns A promise resolving to a unique invite code
|
|
||||||
*/
|
|
||||||
async function createUniqueTeamInviteCode(
|
|
||||||
tx: Transaction,
|
|
||||||
length: number = 8,
|
|
||||||
maxAttempts: number = 5
|
|
||||||
): Promise<string> {
|
|
||||||
let attempts = 0;
|
|
||||||
|
|
||||||
while (attempts < maxAttempts) {
|
|
||||||
const code = Invite.generateCode(length);
|
|
||||||
|
|
||||||
const teams =
|
|
||||||
await tx
|
|
||||||
.select()
|
|
||||||
.from(teamTable)
|
|
||||||
.where(eq(teamTable.inviteCode, code))
|
|
||||||
.execute()
|
|
||||||
|
|
||||||
if (teams.length === 0) {
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
attempts++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we've exceeded max attempts, add timestamp to ensure uniqueness
|
|
||||||
const timestampSuffix = Date.now().toString(36).slice(-4);
|
|
||||||
const baseCode = Invite.generateCode(length - 4);
|
|
||||||
return baseCode + timestampSuffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const create = fn(
|
|
||||||
Info
|
|
||||||
.omit({ members: true })
|
|
||||||
.partial({
|
|
||||||
id: true,
|
|
||||||
inviteCode: true,
|
|
||||||
maxMembers: true,
|
|
||||||
ownerID: true
|
|
||||||
}),
|
|
||||||
async (input) =>
|
|
||||||
createTransaction(async (tx) => {
|
|
||||||
const inviteCode = await createUniqueTeamInviteCode(tx)
|
|
||||||
const id = input.id ?? createID("team");
|
|
||||||
await tx
|
|
||||||
.insert(teamTable)
|
|
||||||
.values({
|
|
||||||
id,
|
|
||||||
inviteCode,
|
|
||||||
slug: input.slug,
|
|
||||||
name: input.name,
|
|
||||||
ownerID: input.ownerID ?? Actor.userID(),
|
|
||||||
maxMembers: input.maxMembers ?? 1,
|
|
||||||
})
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: [teamTable.slug],
|
|
||||||
set: {
|
|
||||||
timeDeleted: null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return id;
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
export const list = () =>
|
|
||||||
useTransaction(async (tx) =>
|
|
||||||
tx
|
|
||||||
.select({
|
|
||||||
steam_accounts: steamTable,
|
|
||||||
teams: teamTable
|
|
||||||
})
|
|
||||||
.from(teamTable)
|
|
||||||
.innerJoin(memberTable, eq(memberTable.teamID, teamTable.id))
|
|
||||||
.innerJoin(steamTable, eq(memberTable.steamID, steamTable.id))
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(memberTable.userID, Actor.userID()),
|
|
||||||
isNull(memberTable.timeDeleted),
|
|
||||||
isNull(steamTable.timeDeleted),
|
|
||||||
isNull(teamTable.timeDeleted),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.execute()
|
|
||||||
.then((rows) => serialize(rows))
|
|
||||||
)
|
|
||||||
|
|
||||||
export const fromSlug = fn(
|
|
||||||
Info.shape.slug,
|
|
||||||
(slug) =>
|
|
||||||
useTransaction((tx) =>
|
|
||||||
tx
|
|
||||||
.select()
|
|
||||||
.from(teamTable)
|
|
||||||
.innerJoin(memberTable, eq(memberTable.teamID, teamTable.id))
|
|
||||||
.innerJoin(steamTable, eq(memberTable.steamID, steamTable.id))
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(memberTable.userID, Actor.userID()),
|
|
||||||
isNull(memberTable.timeDeleted),
|
|
||||||
isNull(steamTable.timeDeleted),
|
|
||||||
isNull(teamTable.timeDeleted),
|
|
||||||
eq(teamTable.slug, slug),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.then((rows) => serialize(rows).at(0))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
export function serialize(
|
|
||||||
input: { teams: typeof teamTable.$inferSelect; steam_accounts: typeof steamTable.$inferSelect | null }[]
|
|
||||||
): z.infer<typeof Info>[] {
|
|
||||||
return pipe(
|
|
||||||
input,
|
|
||||||
groupBy((row) => row.teams.id),
|
|
||||||
values(),
|
|
||||||
map((group) => ({
|
|
||||||
id: group[0].teams.id,
|
|
||||||
slug: group[0].teams.slug,
|
|
||||||
name: group[0].teams.name,
|
|
||||||
ownerID: group[0].teams.ownerID,
|
|
||||||
maxMembers: group[0].teams.maxMembers,
|
|
||||||
inviteCode: group[0].teams.inviteCode,
|
|
||||||
members: group.map(i => i.steam_accounts)
|
|
||||||
.filter((c): c is typeof steamTable.$inferSelect => Boolean(c))
|
|
||||||
.map((item) => Steam.serialize(item))
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import { timestamps, id, ulid } from "../drizzle/types";
|
|
||||||
import {
|
|
||||||
varchar,
|
|
||||||
pgTable,
|
|
||||||
bigint,
|
|
||||||
unique,
|
|
||||||
uniqueIndex,
|
|
||||||
} from "drizzle-orm/pg-core";
|
|
||||||
import { userTable } from "../user/user.sql";
|
|
||||||
import { steamTable } from "../steam/steam.sql";
|
|
||||||
|
|
||||||
export const teamTable = pgTable(
|
|
||||||
"teams",
|
|
||||||
{
|
|
||||||
...id,
|
|
||||||
...timestamps,
|
|
||||||
name: varchar("name", { length: 255 }).notNull(),
|
|
||||||
ownerID: ulid("owner_id")
|
|
||||||
.notNull()
|
|
||||||
.references(() => userTable.id, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
}),
|
|
||||||
inviteCode: varchar("invite_code", { length: 10 }).notNull(),
|
|
||||||
slug: varchar("slug", { length: 255 })
|
|
||||||
.notNull()
|
|
||||||
.references(() => steamTable.username, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
}),
|
|
||||||
maxMembers: bigint("max_members", { mode: "number" }).notNull(),
|
|
||||||
},
|
|
||||||
(team) => [
|
|
||||||
uniqueIndex("idx_team_slug").on(team.slug),
|
|
||||||
unique("idx_team_invite_code").on(team.inviteCode)
|
|
||||||
]
|
|
||||||
);
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Resource } from "sst";
|
|
||||||
import { bus } from "sst/aws/bus";
|
|
||||||
import { Common } from "../common";
|
import { Common } from "../common";
|
||||||
import { createEvent } from "../event";
|
import { createEvent } from "../event";
|
||||||
import { Polar } from "../polar/index";
|
import { Polar } from "../polar/index";
|
||||||
@@ -9,7 +7,7 @@ import { userTable } from "./user.sql";
|
|||||||
import { Examples } from "../examples";
|
import { Examples } from "../examples";
|
||||||
import { and, eq, isNull, asc } from "drizzle-orm";
|
import { and, eq, isNull, asc } from "drizzle-orm";
|
||||||
import { ErrorCodes, VisibleError } from "../error";
|
import { ErrorCodes, VisibleError } from "../error";
|
||||||
import { afterTx, createTransaction, useTransaction } from "../drizzle/transaction";
|
import { createTransaction, useTransaction } from "../drizzle/transaction";
|
||||||
|
|
||||||
export namespace User {
|
export namespace User {
|
||||||
export const Info = z
|
export const Info = z
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
|
export * from "./id"
|
||||||
export * from "./fn"
|
export * from "./fn"
|
||||||
export * from "./log"
|
export * from "./log"
|
||||||
export * from "./id"
|
|
||||||
export * from "./invite"
|
export * from "./invite"
|
||||||
export * from "./token"
|
|
||||||
export * from "./helper"
|
export * from "./helper"
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
import { z } from 'zod';
|
|
||||||
import { fn } from './fn';
|
|
||||||
import crypto from 'crypto';
|
|
||||||
import { Resource } from 'sst';
|
|
||||||
|
|
||||||
// This is a 32-character random ASCII string
|
|
||||||
const rawKey = Resource.SteamEncryptionKey.value;
|
|
||||||
|
|
||||||
// Turn it into exactly 32 bytes via UTF-8
|
|
||||||
const key = Buffer.from(rawKey, 'utf8');
|
|
||||||
if (key.length !== 32) {
|
|
||||||
throw new Error(
|
|
||||||
`SteamEncryptionKey must be exactly 32 bytes; got ${key.length}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ENCRYPTION_IV_LENGTH = 12; // 96 bits for GCM
|
|
||||||
|
|
||||||
export namespace Token {
|
|
||||||
export const encrypt = fn(
|
|
||||||
z.string().min(4),
|
|
||||||
(token) => {
|
|
||||||
const iv = crypto.randomBytes(ENCRYPTION_IV_LENGTH);
|
|
||||||
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
|
||||||
|
|
||||||
const ciphertext = Buffer.concat([
|
|
||||||
cipher.update(token, 'utf8'),
|
|
||||||
cipher.final(),
|
|
||||||
]);
|
|
||||||
const tag = cipher.getAuthTag();
|
|
||||||
|
|
||||||
return ['v1', iv.toString('hex'), tag.toString('hex'), ciphertext.toString('hex')].join(':');
|
|
||||||
});
|
|
||||||
|
|
||||||
export const decrypt = fn(
|
|
||||||
z.string().min(4),
|
|
||||||
(data) => {
|
|
||||||
const [version, ivHex, tagHex, ciphertextHex] = data.split(':');
|
|
||||||
if (version !== 'v1' || !ivHex || !tagHex || !ciphertextHex) {
|
|
||||||
throw new Error('Invalid token format');
|
|
||||||
}
|
|
||||||
|
|
||||||
const iv = Buffer.from(ivHex, 'hex');
|
|
||||||
const tag = Buffer.from(tagHex, 'hex');
|
|
||||||
const ciphertext = Buffer.from(ciphertextHex, 'hex');
|
|
||||||
|
|
||||||
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
|
|
||||||
decipher.setAuthTag(tag);
|
|
||||||
|
|
||||||
const plaintext = Buffer.concat([
|
|
||||||
decipher.update(ciphertext),
|
|
||||||
decipher.final(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return plaintext.toString('utf8');
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
FROM mirror.gcr.io/oven/bun:1.2
|
|
||||||
|
|
||||||
# TODO: Add a way to build C# Steam.exe and start it to run in the container before the API
|
|
||||||
|
|
||||||
ADD ./package.json .
|
|
||||||
ADD ./bun.lock .
|
|
||||||
ADD ./packages/core/package.json ./packages/core/package.json
|
|
||||||
ADD ./packages/functions/package.json ./packages/functions/package.json
|
|
||||||
ADD ./patches ./patches
|
|
||||||
RUN bun install --ignore-scripts
|
|
||||||
|
|
||||||
ADD ./packages/functions ./packages/functions
|
|
||||||
ADD ./packages/core ./packages/core
|
|
||||||
|
|
||||||
WORKDIR ./packages/functions
|
|
||||||
|
|
||||||
CMD ["bun", "run", "./src/api/index.ts"]
|
|
||||||
@@ -7,16 +7,16 @@
|
|||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/steamcommunity": "^3.43.8"
|
"@types/steamcommunity": "^3.43.8"
|
||||||
},
|
},
|
||||||
"scripts": {
|
|
||||||
"dev:auth": "bun run --watch ./src/auth/index.ts",
|
|
||||||
"dev:api": "bun run --watch ./src/api/index.ts"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
},
|
},
|
||||||
|
"exports": {
|
||||||
|
"./*": "./src/*.ts"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actor-core/bun": "^0.8.0",
|
"@actor-core/bun": "^0.8.0",
|
||||||
"@actor-core/file-system": "^0.8.0",
|
"@actor-core/file-system": "^0.8.0",
|
||||||
|
"@aws-sdk/client-lambda": "^3.821.0",
|
||||||
"@aws-sdk/client-s3": "^3.806.0",
|
"@aws-sdk/client-s3": "^3.806.0",
|
||||||
"@aws-sdk/client-sqs": "^3.806.0",
|
"@aws-sdk/client-sqs": "^3.806.0",
|
||||||
"@nestri/core": "workspace:",
|
"@nestri/core": "workspace:",
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export namespace AccountApi {
|
|||||||
schema: Result(
|
schema: Result(
|
||||||
Account.Info.openapi({
|
Account.Info.openapi({
|
||||||
description: "User account information",
|
description: "User account information",
|
||||||
example: { ...Examples.User, teams: [Examples.Team] }
|
example: { ...Examples.User, profiles: [Examples.SteamAccount] }
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import "zod-openapi/extend";
|
import "zod-openapi/extend";
|
||||||
import { cors } from "hono/cors";
|
import { Hono } from "hono";
|
||||||
import { GameApi } from "./game";
|
import { GameApi } from "./game";
|
||||||
import { SteamApi } from "./steam";
|
import { SteamApi } from "./steam";
|
||||||
import { auth } from "./utils/auth";
|
import { auth } from "./utils/auth";
|
||||||
import { FriendApi } from "./friend";
|
import { FriendApi } from "./friend";
|
||||||
import { logger } from "hono/logger";
|
import { logger } from "hono/logger";
|
||||||
import { type Env, Hono } from "hono";
|
|
||||||
import { Realtime } from "./realtime";
|
|
||||||
import { AccountApi } from "./account";
|
import { AccountApi } from "./account";
|
||||||
import { openAPISpecs } from "hono-openapi";
|
import { openAPISpecs } from "hono-openapi";
|
||||||
import { patchLogger } from "../utils/patch-logger";
|
import { patchLogger } from "../utils/patch-logger";
|
||||||
import { HTTPException } from "hono/http-exception";
|
import { HTTPException } from "hono/http-exception";
|
||||||
|
import { handle, streamHandle } from "hono/aws-lambda";
|
||||||
import { ErrorCodes, VisibleError } from "@nestri/core/error";
|
import { ErrorCodes, VisibleError } from "@nestri/core/error";
|
||||||
|
|
||||||
patchLogger();
|
patchLogger();
|
||||||
@@ -18,7 +17,6 @@ patchLogger();
|
|||||||
export const app = new Hono();
|
export const app = new Hono();
|
||||||
app
|
app
|
||||||
.use(logger())
|
.use(logger())
|
||||||
.use(cors())
|
|
||||||
.use(async (c, next) => {
|
.use(async (c, next) => {
|
||||||
c.header("Cache-Control", "no-store");
|
c.header("Cache-Control", "no-store");
|
||||||
return next();
|
return next();
|
||||||
@@ -29,7 +27,6 @@ const routes = app
|
|||||||
.get("/", (c) => c.text("Hello World!"))
|
.get("/", (c) => c.text("Hello World!"))
|
||||||
.route("/games", GameApi.route)
|
.route("/games", GameApi.route)
|
||||||
.route("/steam", SteamApi.route)
|
.route("/steam", SteamApi.route)
|
||||||
.route("/realtime", Realtime.route)
|
|
||||||
.route("/friends", FriendApi.route)
|
.route("/friends", FriendApi.route)
|
||||||
.route("/account", AccountApi.route)
|
.route("/account", AccountApi.route)
|
||||||
.onError((error, c) => {
|
.onError((error, c) => {
|
||||||
@@ -79,9 +76,9 @@ app.get(
|
|||||||
},
|
},
|
||||||
TeamID: {
|
TeamID: {
|
||||||
type: "apiKey",
|
type: "apiKey",
|
||||||
description: "The team ID to use for this query",
|
description: "The steam ID to use for this query",
|
||||||
in: "header",
|
in: "header",
|
||||||
name: "x-nestri-team"
|
name: "x-nestri-steam"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -94,13 +91,6 @@ app.get(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
export default {
|
export type Routes = typeof routes;
|
||||||
port: 3001,
|
|
||||||
idleTimeout: 255,
|
export const handler = process.env.SST_LIVE ? handle(app) : streamHandle(app);
|
||||||
webSocketHandler: Realtime.webSocketHandler,
|
|
||||||
fetch: (req: Request,env: Env) =>
|
|
||||||
app.fetch(req, env, {
|
|
||||||
waitUntil: (fn) => fn,
|
|
||||||
passThroughOnException: () => { },
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
@@ -1,24 +1,14 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import crypto from 'crypto';
|
|
||||||
import { Resource } from "sst";
|
import { Resource } from "sst";
|
||||||
import { streamSSE } from "hono/streaming";
|
|
||||||
import { Actor } from "@nestri/core/actor";
|
|
||||||
import SteamCommunity from "steamcommunity";
|
|
||||||
import { describeRoute } from "hono-openapi";
|
import { describeRoute } from "hono-openapi";
|
||||||
import { Team } from "@nestri/core/team/index";
|
import { User } from "@nestri/core/user/index";
|
||||||
import { Examples } from "@nestri/core/examples";
|
import { Examples } from "@nestri/core/examples";
|
||||||
import { Steam } from "@nestri/core/steam/index";
|
import { Steam } from "@nestri/core/steam/index";
|
||||||
import { Member } from "@nestri/core/member/index";
|
import { getCookie, setCookie } from "hono/cookie";
|
||||||
import { Client } from "@nestri/core/client/index";
|
import { Client } from "@nestri/core/client/index";
|
||||||
import { Library } from "@nestri/core/library/index";
|
import { ErrorCodes, VisibleError } from "@nestri/core/error";
|
||||||
import { chunkArray } from "@nestri/core/utils/helper";
|
import { ErrorResponses, validator, Result, notPublic } from "./utils";
|
||||||
import { ErrorResponses, validator, Result } from "./utils";
|
|
||||||
import { Credentials } from "@nestri/core/credentials/index";
|
|
||||||
import { SendMessageCommand, SQSClient } from "@aws-sdk/client-sqs";
|
|
||||||
import { LoginSession, EAuthTokenPlatformType } from "steam-session";
|
|
||||||
|
|
||||||
const sqs = new SQSClient({});
|
|
||||||
|
|
||||||
export namespace SteamApi {
|
export namespace SteamApi {
|
||||||
export const route = new Hono()
|
export const route = new Hono()
|
||||||
@@ -45,273 +35,131 @@ export namespace SteamApi {
|
|||||||
429: ErrorResponses[429],
|
429: ErrorResponses[429],
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
notPublic,
|
||||||
async (c) =>
|
async (c) =>
|
||||||
c.json({
|
c.json({
|
||||||
data: await Steam.list()
|
data: await Steam.list()
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.get("/login",
|
.get("/callback/:id",
|
||||||
|
validator(
|
||||||
|
"param",
|
||||||
|
z.object({
|
||||||
|
id: z.string().openapi({
|
||||||
|
description: "ID of the user to login",
|
||||||
|
example: Examples.User.id,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
async (c) => {
|
||||||
|
const cookieID = getCookie(c, "user_id");
|
||||||
|
|
||||||
|
const userID = c.req.valid("param").id;
|
||||||
|
|
||||||
|
if (!cookieID || cookieID !== userID) {
|
||||||
|
throw new VisibleError(
|
||||||
|
"authentication",
|
||||||
|
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||||
|
"You should not be here"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentUser = await User.fromID(userID);
|
||||||
|
if (!currentUser) {
|
||||||
|
throw new VisibleError(
|
||||||
|
"not_found",
|
||||||
|
ErrorCodes.NotFound.RESOURCE_NOT_FOUND,
|
||||||
|
`User ${userID} not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = new URL(c.req.url).searchParams;
|
||||||
|
|
||||||
|
// Verify OpenID response and get steamID
|
||||||
|
const steamID = await Client.verifyOpenIDResponse(params);
|
||||||
|
|
||||||
|
// If verification failed, return error
|
||||||
|
if (!steamID) {
|
||||||
|
throw new VisibleError(
|
||||||
|
"authentication",
|
||||||
|
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||||
|
"Invalid OpenID authentication response"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = (await Client.getUserInfo([steamID]))[0];
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new VisibleError(
|
||||||
|
"internal",
|
||||||
|
ErrorCodes.NotFound.RESOURCE_NOT_FOUND,
|
||||||
|
"Steam user data is missing"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const wasAdded = await Steam.create({ ...user, userID });
|
||||||
|
|
||||||
|
if (!wasAdded) {
|
||||||
|
// Update the owner of the Steam account
|
||||||
|
await Steam.updateOwner({ userID, steamID })
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.html(
|
||||||
|
`
|
||||||
|
<script>
|
||||||
|
window.location.href = "about:blank";
|
||||||
|
window.close()
|
||||||
|
</script>
|
||||||
|
`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.get("/popup/:id",
|
||||||
describeRoute({
|
describeRoute({
|
||||||
tags: ["Steam"],
|
tags: ["Steam"],
|
||||||
summary: "Login to Steam using QR code",
|
summary: "Login to Steam",
|
||||||
description: "Login to Steam using a QR code sent using Server Sent Events",
|
description: "Login to Steam in a popup",
|
||||||
responses: {
|
responses: {
|
||||||
400: ErrorResponses[400],
|
400: ErrorResponses[400],
|
||||||
429: ErrorResponses[429],
|
429: ErrorResponses[429],
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
validator(
|
validator(
|
||||||
"header",
|
"param",
|
||||||
z.object({
|
z.object({
|
||||||
"accept": z.string()
|
id: z.string().openapi({
|
||||||
.refine((v) =>
|
description: "ID of the user to login",
|
||||||
v.toLowerCase()
|
example: Examples.User.id,
|
||||||
.includes("text/event-stream")
|
}),
|
||||||
)
|
}),
|
||||||
.openapi({
|
|
||||||
description: "Client must accept Server Sent Events",
|
|
||||||
example: "text/event-stream"
|
|
||||||
})
|
|
||||||
})
|
|
||||||
),
|
),
|
||||||
(c) => {
|
async (c) => {
|
||||||
const currentUser = Actor.user()
|
const userID = c.req.valid("param").id;
|
||||||
|
|
||||||
return streamSSE(c, async (stream) => {
|
const user = await User.fromID(userID);
|
||||||
|
if (!user) {
|
||||||
|
throw new VisibleError(
|
||||||
|
"not_found",
|
||||||
|
ErrorCodes.NotFound.RESOURCE_NOT_FOUND,
|
||||||
|
`User ${userID} not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const session = new LoginSession(EAuthTokenPlatformType.MobileApp);
|
setCookie(c, "user_id", user.id);
|
||||||
|
|
||||||
session.loginTimeout = 30000; //30 seconds is typically when the url expires
|
const returnUrl = `${new URL(Resource.Urls.api).origin}/steam/callback/${userID}`
|
||||||
|
|
||||||
await stream.writeSSE({
|
const params = new URLSearchParams({
|
||||||
event: 'status',
|
'openid.ns': 'http://specs.openid.net/auth/2.0',
|
||||||
data: JSON.stringify({ message: "connected to steam" })
|
'openid.mode': 'checkid_setup',
|
||||||
})
|
'openid.return_to': returnUrl,
|
||||||
|
'openid.realm': new URL(returnUrl).origin,
|
||||||
const challenge = await session.startWithQR();
|
'openid.identity': 'http://specs.openid.net/auth/2.0/identifier_select',
|
||||||
|
'openid.claimed_id': 'http://specs.openid.net/auth/2.0/identifier_select',
|
||||||
await stream.writeSSE({
|
'user_id': user.id
|
||||||
event: 'challenge_url',
|
|
||||||
data: JSON.stringify({ url: challenge.qrChallengeUrl })
|
|
||||||
})
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
session.on('remoteInteraction', async () => {
|
|
||||||
await stream.writeSSE({
|
|
||||||
event: 'remote_interaction',
|
|
||||||
data: JSON.stringify({ message: "Looks like you've scanned the code! Now just approve the login." }),
|
|
||||||
})
|
|
||||||
|
|
||||||
await stream.writeSSE({
|
|
||||||
event: 'status',
|
|
||||||
data: JSON.stringify({ message: "Looks like you've scanned the code! Now just approve the login." }),
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
session.on('timeout', async () => {
|
return c.redirect(`https://steamcommunity.com/openid/login?${params.toString()}`, 302)
|
||||||
console.log('This login attempt has timed out.');
|
|
||||||
|
|
||||||
await stream.writeSSE({
|
|
||||||
event: 'status',
|
|
||||||
data: JSON.stringify({ message: "Your session timed out" }),
|
|
||||||
})
|
|
||||||
|
|
||||||
await stream.writeSSE({
|
|
||||||
event: 'timed_out',
|
|
||||||
data: JSON.stringify({ success: false }),
|
|
||||||
})
|
|
||||||
|
|
||||||
await stream.close()
|
|
||||||
reject("Authentication timed out")
|
|
||||||
});
|
|
||||||
|
|
||||||
session.on('error', async (err) => {
|
|
||||||
// This should ordinarily not happen. This only happens in case there's some kind of unexpected error while
|
|
||||||
// polling, e.g. the network connection goes down or Steam chokes on something.
|
|
||||||
await stream.writeSSE({
|
|
||||||
event: 'status',
|
|
||||||
data: JSON.stringify({ message: "Recieved an error while authenticating" }),
|
|
||||||
})
|
|
||||||
|
|
||||||
await stream.writeSSE({
|
|
||||||
event: 'error',
|
|
||||||
data: JSON.stringify({ message: err.message }),
|
|
||||||
})
|
|
||||||
|
|
||||||
await stream.close()
|
|
||||||
reject(err.message)
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
session.on('authenticated', async () => {
|
|
||||||
await stream.writeSSE({
|
|
||||||
event: 'status',
|
|
||||||
data: JSON.stringify({ message: "Login successful" })
|
|
||||||
})
|
|
||||||
|
|
||||||
await stream.writeSSE({
|
|
||||||
event: 'login_success',
|
|
||||||
data: JSON.stringify({ success: true, })
|
|
||||||
})
|
|
||||||
|
|
||||||
const username = session.accountName;
|
|
||||||
const accessToken = session.accessToken;
|
|
||||||
const refreshToken = session.refreshToken;
|
|
||||||
const steamID = session.steamID.toString();
|
|
||||||
const cookies = await session.getWebCookies();
|
|
||||||
|
|
||||||
// Get user information
|
|
||||||
const community = new SteamCommunity();
|
|
||||||
community.setCookies(cookies);
|
|
||||||
|
|
||||||
const user = await Client.getUserInfo({ steamID, cookies })
|
|
||||||
|
|
||||||
const wasAdded =
|
|
||||||
await Steam.create({
|
|
||||||
username,
|
|
||||||
id: steamID,
|
|
||||||
name: user.name,
|
|
||||||
realName: user.realName,
|
|
||||||
userID: currentUser.userID,
|
|
||||||
avatarHash: user.avatarHash,
|
|
||||||
steamMemberSince: user.memberSince,
|
|
||||||
profileUrl: user.customURL?.trim() || null,
|
|
||||||
limitations: {
|
|
||||||
isLimited: user.isLimitedAccount,
|
|
||||||
isVacBanned: user.vacBanned,
|
|
||||||
privacyState: user.privacyState as any,
|
|
||||||
visibilityState: Number(user.visibilityState),
|
|
||||||
tradeBanState: user.tradeBanState.toLowerCase() as any,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Does not matter if the user is already there or has just been created, just store the credentials
|
|
||||||
await Credentials.create({ refreshToken, steamID, username })
|
|
||||||
|
|
||||||
let teamID: string | undefined
|
|
||||||
|
|
||||||
if (wasAdded) {
|
|
||||||
const rawFirst = (user.name ?? username).trim().split(/\s+/)[0] ?? username;
|
|
||||||
|
|
||||||
const firstName = rawFirst
|
|
||||||
.charAt(0) // first character
|
|
||||||
.toUpperCase() // make it uppercase
|
|
||||||
+ rawFirst
|
|
||||||
.slice(1) // rest of the string
|
|
||||||
.toLowerCase();
|
|
||||||
|
|
||||||
// create a team
|
|
||||||
teamID = await Team.create({
|
|
||||||
slug: username,
|
|
||||||
name: firstName,
|
|
||||||
ownerID: currentUser.userID,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Add us as a member
|
|
||||||
await Actor.provide(
|
|
||||||
"system",
|
|
||||||
{ teamID },
|
|
||||||
async () =>
|
|
||||||
await Member.create({
|
|
||||||
role: "adult",
|
|
||||||
userID: currentUser.userID,
|
|
||||||
steamID
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Update the owner of the Steam account
|
|
||||||
await Steam.updateOwner({ userID: currentUser.userID, steamID })
|
|
||||||
const t = await Actor.provide(
|
|
||||||
"user",
|
|
||||||
currentUser,
|
|
||||||
async () => {
|
|
||||||
// Get the team associated with this username
|
|
||||||
const team = await Team.fromSlug(username);
|
|
||||||
// This should never happen
|
|
||||||
if (!team) throw Error(`Is Nestri okay???, we could not find the team with this slug ${username}`)
|
|
||||||
|
|
||||||
teamID = team.id
|
|
||||||
|
|
||||||
return team.id
|
|
||||||
}
|
|
||||||
)
|
|
||||||
console.log("t",t)
|
|
||||||
console.log("teamID",teamID)
|
|
||||||
}
|
|
||||||
|
|
||||||
await stream.writeSSE({
|
|
||||||
event: 'team_slug',
|
|
||||||
data: JSON.stringify({ username })
|
|
||||||
})
|
|
||||||
|
|
||||||
// Get game library in the background
|
|
||||||
c.executionCtx.waitUntil((async () => {
|
|
||||||
const games = await Client.getUserLibrary(accessToken);
|
|
||||||
|
|
||||||
// Get a batch of 5 games each
|
|
||||||
const apps = games?.response?.apps || [];
|
|
||||||
if (apps.length === 0) {
|
|
||||||
console.info("[SteamApi] Is Steam okay? No games returned for user:", { steamID });
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const chunkedGames = chunkArray(apps, 5);
|
|
||||||
// Get the batches to the queue
|
|
||||||
const processQueue = chunkedGames.map(async (chunk) => {
|
|
||||||
const myGames = chunk.map(i => {
|
|
||||||
return {
|
|
||||||
appID: i.appid,
|
|
||||||
totalPlaytime: i.rt_playtime,
|
|
||||||
isFamilyShareable: i.exclude_reason === 0,
|
|
||||||
lastPlayed: new Date(i.rt_last_played * 1000),
|
|
||||||
timeAcquired: new Date(i.rt_time_acquired * 1000),
|
|
||||||
isFamilyShared: !i.owner_steamids.includes(steamID) && i.exclude_reason === 0,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (teamID) {
|
|
||||||
const deduplicationId = crypto
|
|
||||||
.createHash('md5')
|
|
||||||
.update(`${teamID}_${chunk.map(g => g.appid).join(',')}`)
|
|
||||||
.digest('hex');
|
|
||||||
|
|
||||||
await Actor.provide(
|
|
||||||
"member",
|
|
||||||
{
|
|
||||||
teamID,
|
|
||||||
steamID,
|
|
||||||
userID: currentUser.userID
|
|
||||||
},
|
|
||||||
async () => {
|
|
||||||
const payload = await Library.Events.Queue.create(myGames);
|
|
||||||
|
|
||||||
await sqs.send(
|
|
||||||
new SendMessageCommand({
|
|
||||||
MessageGroupId: teamID,
|
|
||||||
QueueUrl: Resource.LibraryQueue.url,
|
|
||||||
MessageBody: JSON.stringify(payload),
|
|
||||||
MessageDeduplicationId: deduplicationId,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const settled = await Promise.allSettled(processQueue)
|
|
||||||
|
|
||||||
settled
|
|
||||||
.filter(r => r.status === "rejected")
|
|
||||||
.forEach(r => console.error("[LibraryQueue] enqueue failed:", (r as PromiseRejectedResult).reason));
|
|
||||||
})())
|
|
||||||
|
|
||||||
await stream.close();
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
import { z } from "zod"
|
|
||||||
import { Hono } from "hono";
|
|
||||||
import { describeRoute } from "hono-openapi";
|
|
||||||
import { Team } from "@nestri/core/team/index";
|
|
||||||
import { Examples } from "@nestri/core/examples";
|
|
||||||
import { ErrorResponses, Result, validator } from "./utils";
|
|
||||||
import { ErrorCodes, VisibleError } from "@nestri/core/error";
|
|
||||||
|
|
||||||
export namespace TeamApi {
|
|
||||||
export const route = new Hono()
|
|
||||||
.get("/",
|
|
||||||
describeRoute({
|
|
||||||
tags: ["Team"],
|
|
||||||
summary: "List user teams",
|
|
||||||
description: "List the current user's team details",
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: Result(
|
|
||||||
Team.Info.array().openapi({
|
|
||||||
description: "All team information",
|
|
||||||
example: [Examples.Team]
|
|
||||||
})
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: "All team details"
|
|
||||||
},
|
|
||||||
400: ErrorResponses[400],
|
|
||||||
404: ErrorResponses[404],
|
|
||||||
429: ErrorResponses[429],
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
async (c) =>
|
|
||||||
c.json({
|
|
||||||
data: await Team.list()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.get("/:slug",
|
|
||||||
describeRoute({
|
|
||||||
tags: ["Team"],
|
|
||||||
summary: "Get team by slug",
|
|
||||||
description: "Get the current user's team details, by its slug",
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: Result(
|
|
||||||
Team.Info.openapi({
|
|
||||||
description: "Team details",
|
|
||||||
example: Examples.Team
|
|
||||||
})
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: "Team details"
|
|
||||||
},
|
|
||||||
400: ErrorResponses[400],
|
|
||||||
404: ErrorResponses[404],
|
|
||||||
429: ErrorResponses[429],
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
validator(
|
|
||||||
"param",
|
|
||||||
z.object({
|
|
||||||
slug: z.string().openapi({
|
|
||||||
description: "SLug of the team to get",
|
|
||||||
example: Examples.Team.slug,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
async (c) => {
|
|
||||||
const teamSlug = c.req.valid("param").slug
|
|
||||||
|
|
||||||
const team = await Team.fromSlug(teamSlug)
|
|
||||||
|
|
||||||
if (!team) {
|
|
||||||
throw new VisibleError(
|
|
||||||
"not_found",
|
|
||||||
ErrorCodes.NotFound.RESOURCE_NOT_FOUND,
|
|
||||||
`Team ${teamSlug} not found`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.json({
|
|
||||||
data: team
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,9 @@ import { Resource } from "sst";
|
|||||||
import { subjects } from "../../subjects";
|
import { subjects } from "../../subjects";
|
||||||
import { Actor } from "@nestri/core/actor";
|
import { Actor } from "@nestri/core/actor";
|
||||||
import { type MiddlewareHandler } from "hono";
|
import { type MiddlewareHandler } from "hono";
|
||||||
|
import { Steam } from "@nestri/core/steam/index";
|
||||||
import { createClient } from "@openauthjs/openauth/client";
|
import { createClient } from "@openauthjs/openauth/client";
|
||||||
import { ErrorCodes, VisibleError } from "@nestri/core/error";
|
import { ErrorCodes, VisibleError } from "@nestri/core/error";
|
||||||
import { Member } from "@nestri/core/member/index";
|
|
||||||
|
|
||||||
const client = createClient({
|
const client = createClient({
|
||||||
clientID: "api",
|
clientID: "api",
|
||||||
@@ -44,19 +44,19 @@ export const auth: MiddlewareHandler = async (c, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (result.subject.type === "user") {
|
if (result.subject.type === "user") {
|
||||||
const teamID = c.req.header("x-nestri-team");
|
const steamID = c.req.header("x-nestri-steam");
|
||||||
if (!teamID) {
|
if (!steamID) {
|
||||||
return Actor.provide(result.subject.type, result.subject.properties, next);
|
return Actor.provide(result.subject.type, result.subject.properties, next);
|
||||||
}
|
}
|
||||||
const userID = result.subject.properties.userID
|
const userID = result.subject.properties.userID
|
||||||
return Actor.provide(
|
return Actor.provide(
|
||||||
"system",
|
"steam",
|
||||||
{
|
{
|
||||||
teamID
|
steamID
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
const member = await Member.fromUserID(userID)
|
const steamAcc = await Steam.confirmOwnerShip(userID)
|
||||||
if (!member || !member.userID) {
|
if (!steamAcc) {
|
||||||
throw new VisibleError(
|
throw new VisibleError(
|
||||||
"authentication",
|
"authentication",
|
||||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||||
@@ -66,9 +66,8 @@ export const auth: MiddlewareHandler = async (c, next) => {
|
|||||||
return Actor.provide(
|
return Actor.provide(
|
||||||
"member",
|
"member",
|
||||||
{
|
{
|
||||||
steamID: member.steamID,
|
steamID,
|
||||||
userID: member.userID,
|
userID,
|
||||||
teamID: member.teamID
|
|
||||||
},
|
},
|
||||||
next)
|
next)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,24 +1,19 @@
|
|||||||
import { Resource } from "sst";
|
import { Resource } from "sst";
|
||||||
import { type Env } from "hono";
|
|
||||||
import { logger } from "hono/logger";
|
import { logger } from "hono/logger";
|
||||||
import { subjects } from "../subjects";
|
import { subjects } from "../subjects";
|
||||||
|
import { handle } from "hono/aws-lambda";
|
||||||
import { PasswordUI, Select } from "./ui";
|
import { PasswordUI, Select } from "./ui";
|
||||||
import { issuer } from "@openauthjs/openauth";
|
import { issuer } from "@openauthjs/openauth";
|
||||||
import { User } from "@nestri/core/user/index";
|
import { User } from "@nestri/core/user/index";
|
||||||
import { Email } from "@nestri/core/email/index";
|
import { Email } from "@nestri/core/email/index";
|
||||||
import { patchLogger } from "../utils/patch-logger";
|
import { patchLogger } from "../utils/patch-logger";
|
||||||
import { handleDiscord, handleGithub } from "./utils";
|
import { handleDiscord, handleGithub } from "./utils";
|
||||||
import { MemoryStorage } from "@openauthjs/openauth/storage/memory";
|
|
||||||
import { DiscordAdapter, PasswordAdapter, GithubAdapter } from "./adapters";
|
import { DiscordAdapter, PasswordAdapter, GithubAdapter } from "./adapters";
|
||||||
|
|
||||||
patchLogger();
|
patchLogger();
|
||||||
|
|
||||||
const app = issuer({
|
const app = issuer({
|
||||||
//TODO: Create our own Storage (?)
|
|
||||||
select: Select(),
|
select: Select(),
|
||||||
storage: MemoryStorage({
|
|
||||||
persist: process.env.STORAGE
|
|
||||||
}),
|
|
||||||
theme: {
|
theme: {
|
||||||
title: "Nestri | Auth",
|
title: "Nestri | Auth",
|
||||||
primary: "#FF4F01",
|
primary: "#FF4F01",
|
||||||
@@ -161,13 +156,4 @@ const app = issuer({
|
|||||||
},
|
},
|
||||||
}).use(logger())
|
}).use(logger())
|
||||||
|
|
||||||
|
export const handler = handle(app);
|
||||||
export default {
|
|
||||||
port: 3002,
|
|
||||||
idleTimeout: 255,
|
|
||||||
fetch: (req: Request, env: Env) =>
|
|
||||||
app.fetch(req, env, {
|
|
||||||
waitUntil: (fn) => fn,
|
|
||||||
passThroughOnException: () => { },
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
@@ -1,74 +1,37 @@
|
|||||||
|
import "zod-openapi/extend";
|
||||||
import { Resource } from "sst";
|
import { Resource } from "sst";
|
||||||
import { bus } from "sst/aws/bus";
|
import { bus } from "sst/aws/bus";
|
||||||
|
import { Actor } from "@nestri/core/actor";
|
||||||
|
import { Game } from "@nestri/core/game/index";
|
||||||
import { Steam } from "@nestri/core/steam/index";
|
import { Steam } from "@nestri/core/steam/index";
|
||||||
import { Client } from "@nestri/core/client/index";
|
import { Client } from "@nestri/core/client/index";
|
||||||
import { Images } from "@nestri/core/images/index";
|
|
||||||
import { Friend } from "@nestri/core/friend/index";
|
import { Friend } from "@nestri/core/friend/index";
|
||||||
|
import { Images } from "@nestri/core/images/index";
|
||||||
|
import { Library } from "@nestri/core/library/index";
|
||||||
|
import { chunkArray } from "@nestri/core/utils/index";
|
||||||
import { BaseGame } from "@nestri/core/base-game/index";
|
import { BaseGame } from "@nestri/core/base-game/index";
|
||||||
import { Credentials } from "@nestri/core/credentials/index";
|
import { Categories } from "@nestri/core/categories/index";
|
||||||
import { EAuthTokenPlatformType, LoginSession } from "steam-session";
|
import { ImageTypeEnum } from "@nestri/core/images/images.sql";
|
||||||
import { PutObjectCommand, S3Client, HeadObjectCommand } from "@aws-sdk/client-s3";
|
import { PutObjectCommand, S3Client, HeadObjectCommand } from "@aws-sdk/client-s3";
|
||||||
|
|
||||||
const s3 = new S3Client({});
|
const s3 = new S3Client({});
|
||||||
|
|
||||||
export const handler = bus.subscriber(
|
export const handler = bus.subscriber(
|
||||||
[Credentials.Events.New, BaseGame.Events.New],
|
[
|
||||||
|
Library.Events.Add,
|
||||||
|
BaseGame.Events.New,
|
||||||
|
Steam.Events.Updated,
|
||||||
|
Steam.Events.Created,
|
||||||
|
BaseGame.Events.NewBoxArt,
|
||||||
|
BaseGame.Events.NewHeroArt,
|
||||||
|
],
|
||||||
async (event) => {
|
async (event) => {
|
||||||
console.log(event.type, event.properties, event.metadata);
|
console.log(event.type, event.properties, event.metadata);
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case "new_credentials.added": {
|
case "new_image.save": {
|
||||||
const input = event.properties
|
const input = event.properties;
|
||||||
const credentials = await Credentials.fromSteamID(input.steamID)
|
const image = await Client.getImageInfo({ url: input.url, type: input.type });
|
||||||
if (credentials) {
|
|
||||||
const session = new LoginSession(EAuthTokenPlatformType.MobileApp);
|
|
||||||
|
|
||||||
session.refreshToken = credentials.refreshToken;
|
|
||||||
|
|
||||||
const cookies = await session.getWebCookies();
|
|
||||||
|
|
||||||
const friends = await Client.getFriendsList(cookies);
|
|
||||||
|
|
||||||
const putFriends = friends.map(async (user) => {
|
|
||||||
const wasAdded =
|
|
||||||
await Steam.create({
|
|
||||||
id: user.steamID.toString(),
|
|
||||||
name: user.name,
|
|
||||||
realName: user.realName,
|
|
||||||
avatarHash: user.avatarHash,
|
|
||||||
steamMemberSince: user.memberSince,
|
|
||||||
profileUrl: user.customURL?.trim() || null,
|
|
||||||
limitations: {
|
|
||||||
isLimited: user.isLimitedAccount,
|
|
||||||
isVacBanned: user.vacBanned,
|
|
||||||
tradeBanState: user.tradeBanState.toLowerCase() as any,
|
|
||||||
privacyState: user.privacyState as any,
|
|
||||||
visibilityState: Number(user.visibilityState)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!wasAdded) {
|
|
||||||
console.log(`Steam user ${user.steamID.toString()} already exists`)
|
|
||||||
}
|
|
||||||
|
|
||||||
await Friend.add({ friendSteamID: user.steamID.toString(), steamID: input.steamID })
|
|
||||||
})
|
|
||||||
|
|
||||||
const settled = await Promise.allSettled(putFriends);
|
|
||||||
|
|
||||||
settled
|
|
||||||
.filter(result => result.status === 'rejected')
|
|
||||||
.forEach(result => console.warn('[putFriends] failed:', (result as PromiseRejectedResult).reason))
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "new_game.added": {
|
|
||||||
const input = event.properties
|
|
||||||
// Get images and save to s3
|
|
||||||
const images = await Client.getImages(input.appID);
|
|
||||||
|
|
||||||
(await Promise.allSettled(
|
|
||||||
images.map(async (image) => {
|
|
||||||
// Put the images into the db
|
|
||||||
await Images.create({
|
await Images.create({
|
||||||
type: image.type,
|
type: image.type,
|
||||||
imageHash: image.hash,
|
imageHash: image.hash,
|
||||||
@@ -85,7 +48,7 @@ export const handler = bus.subscriber(
|
|||||||
await s3.send(
|
await s3.send(
|
||||||
new HeadObjectCommand({
|
new HeadObjectCommand({
|
||||||
Bucket: Resource.Storage.name,
|
Bucket: Resource.Storage.name,
|
||||||
Key: `images/${image.hash}`,
|
Key: `public/${image.hash}`,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -94,7 +57,7 @@ export const handler = bus.subscriber(
|
|||||||
await s3.send(
|
await s3.send(
|
||||||
new PutObjectCommand({
|
new PutObjectCommand({
|
||||||
Bucket: Resource.Storage.name,
|
Bucket: Resource.Storage.name,
|
||||||
Key: `images/${image.hash}`,
|
Key: `public/${image.hash}`,
|
||||||
Body: image.buffer,
|
Body: image.buffer,
|
||||||
...(image.format && { ContentType: `image/${image.format}` }),
|
...(image.format && { ContentType: `image/${image.format}` }),
|
||||||
Metadata: {
|
Metadata: {
|
||||||
@@ -105,11 +68,289 @@ export const handler = bus.subscriber(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
break;
|
||||||
))
|
}
|
||||||
.filter(i => i.status === "rejected")
|
case "new_box_art_image.save": {
|
||||||
.forEach(r => console.warn("[createImages] failed:", (r as PromiseRejectedResult).reason));
|
const input = event.properties;
|
||||||
|
|
||||||
|
const image = await Client.createBoxArt(input);
|
||||||
|
|
||||||
|
await Images.create({
|
||||||
|
type: image.type,
|
||||||
|
imageHash: image.hash,
|
||||||
|
baseGameID: input.appID,
|
||||||
|
position: image.position,
|
||||||
|
fileSize: image.fileSize,
|
||||||
|
sourceUrl: image.sourceUrl,
|
||||||
|
dimensions: image.dimensions,
|
||||||
|
extractedColor: image.averageColor,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
//Check whether the image already exists
|
||||||
|
await s3.send(
|
||||||
|
new HeadObjectCommand({
|
||||||
|
Bucket: Resource.Storage.name,
|
||||||
|
Key: `public/${image.hash}`,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
// Save to s3 because it doesn't already exist
|
||||||
|
await s3.send(
|
||||||
|
new PutObjectCommand({
|
||||||
|
Bucket: Resource.Storage.name,
|
||||||
|
Key: `public/${image.hash}`,
|
||||||
|
Body: image.buffer,
|
||||||
|
...(image.format && { ContentType: `image/${image.format}` }),
|
||||||
|
Metadata: {
|
||||||
|
type: image.type,
|
||||||
|
appID: input.appID,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "new_hero_art_image.save": {
|
||||||
|
const input = event.properties;
|
||||||
|
|
||||||
|
const images = await Client.createHeroArt(input);
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
images.map(async (image) => {
|
||||||
|
await Images.create({
|
||||||
|
type: image.type,
|
||||||
|
imageHash: image.hash,
|
||||||
|
baseGameID: input.appID,
|
||||||
|
position: image.position,
|
||||||
|
fileSize: image.fileSize,
|
||||||
|
sourceUrl: image.sourceUrl,
|
||||||
|
dimensions: image.dimensions,
|
||||||
|
extractedColor: image.averageColor,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
//Check whether the image already exists
|
||||||
|
await s3.send(
|
||||||
|
new HeadObjectCommand({
|
||||||
|
Bucket: Resource.Storage.name,
|
||||||
|
Key: `public/${image.hash}`,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
// Save to s3 because it doesn't already exist
|
||||||
|
await s3.send(
|
||||||
|
new PutObjectCommand({
|
||||||
|
Bucket: Resource.Storage.name,
|
||||||
|
Key: `public/${image.hash}`,
|
||||||
|
Body: image.buffer,
|
||||||
|
...(image.format && { ContentType: `image/${image.format}` }),
|
||||||
|
Metadata: {
|
||||||
|
type: image.type,
|
||||||
|
appID: input.appID,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "library.add": {
|
||||||
|
|
||||||
|
await Actor.provide(
|
||||||
|
event.metadata.actor.type,
|
||||||
|
event.metadata.actor.properties,
|
||||||
|
async () => {
|
||||||
|
const game = event.properties
|
||||||
|
// First check whether the base_game exists, if not get it
|
||||||
|
const appID = game.appID.toString();
|
||||||
|
const exists = await BaseGame.fromID(appID);
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
const appInfo = await Client.getAppInfo(appID);
|
||||||
|
|
||||||
|
await BaseGame.create({
|
||||||
|
id: appID,
|
||||||
|
name: appInfo.name,
|
||||||
|
size: appInfo.size,
|
||||||
|
slug: appInfo.slug,
|
||||||
|
links: appInfo.links,
|
||||||
|
score: appInfo.score,
|
||||||
|
description: appInfo.description,
|
||||||
|
releaseDate: appInfo.releaseDate,
|
||||||
|
primaryGenre: appInfo.primaryGenre,
|
||||||
|
compatibility: appInfo.compatibility,
|
||||||
|
controllerSupport: appInfo.controllerSupport,
|
||||||
|
})
|
||||||
|
|
||||||
|
const allCategories = [...appInfo.tags, ...appInfo.genres, ...appInfo.publishers, ...appInfo.developers, ...appInfo.categories, ...appInfo.franchises]
|
||||||
|
|
||||||
|
const uniqueCategories = Array.from(
|
||||||
|
new Map(allCategories.map(c => [`${c.type}:${c.slug}`, c])).values()
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
uniqueCategories.map(async (cat) => {
|
||||||
|
//Create category if it doesn't exist
|
||||||
|
await Categories.create({
|
||||||
|
type: cat.type, slug: cat.slug, name: cat.name
|
||||||
|
})
|
||||||
|
|
||||||
|
//Create game if it doesn't exist
|
||||||
|
await Game.create({ baseGameID: appID, categorySlug: cat.slug, categoryType: cat.type })
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const imageUrls = appInfo.images
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
ImageTypeEnum.enumValues.map(async (type) => {
|
||||||
|
switch (type) {
|
||||||
|
case "backdrop": {
|
||||||
|
await bus.publish(Resource.Bus, BaseGame.Events.New, { appID, type: "backdrop", url: imageUrls.backdrop })
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "banner": {
|
||||||
|
await bus.publish(Resource.Bus, BaseGame.Events.New, { appID, type: "banner", url: imageUrls.banner })
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "icon": {
|
||||||
|
await bus.publish(Resource.Bus, BaseGame.Events.New, { appID, type: "icon", url: imageUrls.icon })
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "logo": {
|
||||||
|
await bus.publish(Resource.Bus, BaseGame.Events.New, { appID, type: "logo", url: imageUrls.logo })
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "poster": {
|
||||||
|
await bus.publish(
|
||||||
|
Resource.Bus,
|
||||||
|
BaseGame.Events.New,
|
||||||
|
{ appID, type: "poster", url: imageUrls.poster }
|
||||||
|
)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "heroArt": {
|
||||||
|
await bus.publish(
|
||||||
|
Resource.Bus,
|
||||||
|
BaseGame.Events.NewHeroArt,
|
||||||
|
{ appID, backdropUrl: imageUrls.backdrop, screenshots: imageUrls.screenshots }
|
||||||
|
)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "boxArt": {
|
||||||
|
await bus.publish(
|
||||||
|
Resource.Bus,
|
||||||
|
BaseGame.Events.NewBoxArt,
|
||||||
|
{ appID, logoUrl: imageUrls.logo, backgroundUrl: imageUrls.backdrop }
|
||||||
|
)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to user's library
|
||||||
|
await Library.add({
|
||||||
|
baseGameID: appID,
|
||||||
|
lastPlayed: game.lastPlayed ? new Date(game.lastPlayed) : null,
|
||||||
|
totalPlaytime: game.totalPlaytime,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "steam_account.created":
|
||||||
|
case "steam_account.updated": {
|
||||||
|
const userID = event.properties.userID;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const steamID = event.properties.steamID;
|
||||||
|
// Get friends info
|
||||||
|
const friends = await Client.getFriendsList(steamID);
|
||||||
|
|
||||||
|
const friendSteamIDs = friends.friendslist.friends.map(f => f.steamid);
|
||||||
|
|
||||||
|
// Steam API has a limit of requesting 100 friends at a go
|
||||||
|
const friendChunks = chunkArray(friendSteamIDs, 100);
|
||||||
|
|
||||||
|
const settled = await Promise.allSettled(
|
||||||
|
friendChunks.map(async (friendIDs) => {
|
||||||
|
const friendsInfo = await Client.getUserInfo(friendIDs)
|
||||||
|
|
||||||
|
return await Promise.all(
|
||||||
|
friendsInfo.map(async (friend) => {
|
||||||
|
const wasAdded = await Steam.create(friend);
|
||||||
|
|
||||||
|
if (!wasAdded) {
|
||||||
|
console.log(`Friend ${friend.id} already exists`)
|
||||||
|
}
|
||||||
|
|
||||||
|
await Friend.add({ friendSteamID: friend.id, steamID })
|
||||||
|
|
||||||
|
return friend.id
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
settled
|
||||||
|
.filter(result => result.status === 'rejected')
|
||||||
|
.forEach(result => console.warn('[putFriends] failed:', (result as PromiseRejectedResult).reason))
|
||||||
|
|
||||||
|
const prod = (Resource.App.stage === "production" || Resource.App.stage === "dev")
|
||||||
|
|
||||||
|
const friendIDs = [
|
||||||
|
steamID,
|
||||||
|
...(prod ? settled
|
||||||
|
.filter(result => result.status === "fulfilled")
|
||||||
|
.map(f => f.value)
|
||||||
|
.flat() : [])
|
||||||
|
]
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
friendIDs.map(async (currentSteamID) => {
|
||||||
|
// Get user library
|
||||||
|
const gameLibrary = await Client.getUserLibrary(currentSteamID);
|
||||||
|
|
||||||
|
const queryLib = await Promise.allSettled(
|
||||||
|
gameLibrary.response.games.map(async (game) => {
|
||||||
|
await Actor.provide(
|
||||||
|
"steam",
|
||||||
|
{
|
||||||
|
steamID: currentSteamID,
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
|
||||||
|
await bus.publish(
|
||||||
|
Resource.Bus,
|
||||||
|
Library.Events.Add,
|
||||||
|
{
|
||||||
|
appID: game.appid,
|
||||||
|
totalPlaytime: game.playtime_forever,
|
||||||
|
lastPlayed: game.rtime_last_played ? new Date(game.rtime_last_played * 1000) : null,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
queryLib
|
||||||
|
.filter(i => i.status === "rejected")
|
||||||
|
.forEach(e => console.warn(`[pushUserLib]: Failed to push user library to queue: ${e.reason}`))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(`Failed to process Steam data for user ${userID}:`, error);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
import { SQSHandler } from "aws-lambda";
|
|
||||||
import { Actor } from "@nestri/core/actor";
|
|
||||||
import { Game } from "@nestri/core/game/index";
|
|
||||||
import { Utils } from "@nestri/core/client/utils";
|
|
||||||
import { Client } from "@nestri/core/client/index";
|
|
||||||
import { Library } from "@nestri/core/library/index";
|
|
||||||
import { BaseGame } from "@nestri/core/base-game/index";
|
|
||||||
import { Categories } from "@nestri/core/categories/index";
|
|
||||||
|
|
||||||
export const handler: SQSHandler = async (event) => {
|
|
||||||
for (const record of event.Records) {
|
|
||||||
const parsed = JSON.parse(
|
|
||||||
record.body,
|
|
||||||
) as typeof Library.Events.Queue.$payload;
|
|
||||||
|
|
||||||
await Actor.provide(
|
|
||||||
parsed.metadata.actor.type,
|
|
||||||
parsed.metadata.actor.properties,
|
|
||||||
async () => {
|
|
||||||
const processGames = parsed.properties.map(async (game) => {
|
|
||||||
// First check whether the base_game exists, if not get it
|
|
||||||
const appID = game.appID.toString()
|
|
||||||
const exists = await BaseGame.fromID(appID)
|
|
||||||
|
|
||||||
if (!exists) {
|
|
||||||
const appInfo = await Client.getAppInfo(appID);
|
|
||||||
const tags = appInfo.tags;
|
|
||||||
|
|
||||||
await BaseGame.create({
|
|
||||||
id: appID,
|
|
||||||
name: appInfo.name,
|
|
||||||
size: appInfo.size,
|
|
||||||
score: appInfo.score,
|
|
||||||
slug: appInfo.slug,
|
|
||||||
description: appInfo.description,
|
|
||||||
releaseDate: appInfo.releaseDate,
|
|
||||||
primaryGenre: appInfo.primaryGenre,
|
|
||||||
compatibility: appInfo.compatibility,
|
|
||||||
controllerSupport: appInfo.controllerSupport,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (game.isFamilyShareable) {
|
|
||||||
tags.push(Utils.createTag("Family Share"))
|
|
||||||
}
|
|
||||||
|
|
||||||
const allCategories = [...tags, ...appInfo.genres, ...appInfo.publishers, ...appInfo.developers]
|
|
||||||
|
|
||||||
const uniqueCategories = Array.from(
|
|
||||||
new Map(allCategories.map(c => [`${c.type}:${c.slug}`, c])).values()
|
|
||||||
);
|
|
||||||
|
|
||||||
const settled = await Promise.allSettled(
|
|
||||||
uniqueCategories.map(async (cat) => {
|
|
||||||
//Use a single db transaction to get or set the category
|
|
||||||
await Categories.create({
|
|
||||||
type: cat.type, slug: cat.slug, name: cat.name
|
|
||||||
})
|
|
||||||
|
|
||||||
// Use a single db transaction to get or create the game
|
|
||||||
await Game.create({ baseGameID: appID, categorySlug: cat.slug, categoryType: cat.type })
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
settled
|
|
||||||
.filter(r => r.status === "rejected")
|
|
||||||
.forEach(r => console.warn("[uniqueCategories] failed:", (r as PromiseRejectedResult).reason));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to user's library
|
|
||||||
await Library.add({
|
|
||||||
baseGameID: appID,
|
|
||||||
lastPlayed: game.lastPlayed,
|
|
||||||
timeAcquired: game.timeAcquired,
|
|
||||||
totalPlaytime: game.totalPlaytime,
|
|
||||||
isFamilyShared: game.isFamilyShared,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const settled = await Promise.allSettled(processGames)
|
|
||||||
|
|
||||||
settled
|
|
||||||
.filter(r => r.status === "rejected")
|
|
||||||
.forEach(r => console.warn("[processGames] failed:", (r as PromiseRejectedResult).reason));
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
93
packages/functions/src/queues/retry.ts
Normal file
93
packages/functions/src/queues/retry.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import { Resource } from "sst";
|
||||||
|
import type { SQSHandler } from "aws-lambda";
|
||||||
|
import {
|
||||||
|
SQSClient,
|
||||||
|
SendMessageCommand
|
||||||
|
} from "@aws-sdk/client-sqs";
|
||||||
|
import {
|
||||||
|
LambdaClient,
|
||||||
|
InvokeCommand,
|
||||||
|
GetFunctionCommand,
|
||||||
|
ResourceNotFoundException,
|
||||||
|
} from "@aws-sdk/client-lambda";
|
||||||
|
|
||||||
|
const lambda = new LambdaClient({});
|
||||||
|
lambda.middlewareStack.remove("recursionDetectionMiddleware");
|
||||||
|
const sqs = new SQSClient({});
|
||||||
|
sqs.middlewareStack.remove("recursionDetectionMiddleware");
|
||||||
|
|
||||||
|
export const handler: SQSHandler = async (evt) => {
|
||||||
|
for (const record of evt.Records) {
|
||||||
|
const parsed = JSON.parse(record.body);
|
||||||
|
console.log("body", parsed);
|
||||||
|
const functionName = parsed.requestContext.functionArn
|
||||||
|
.replace(":$LATEST", "")
|
||||||
|
.split(":")
|
||||||
|
.pop();
|
||||||
|
if (parsed.responsePayload) {
|
||||||
|
const attempt = (parsed.requestPayload.attempts || 0) + 1;
|
||||||
|
|
||||||
|
const info = await lambda.send(
|
||||||
|
new GetFunctionCommand({
|
||||||
|
FunctionName: functionName,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const max =
|
||||||
|
Number.parseInt(
|
||||||
|
info.Configuration?.Environment?.Variables?.RETRIES || "",
|
||||||
|
) || 0;
|
||||||
|
console.log("max retries", max);
|
||||||
|
if (attempt > max) {
|
||||||
|
console.log(`giving up after ${attempt} retries`);
|
||||||
|
// send to dlq
|
||||||
|
await sqs.send(
|
||||||
|
new SendMessageCommand({
|
||||||
|
QueueUrl: Resource.Dlq.url,
|
||||||
|
MessageBody: JSON.stringify({
|
||||||
|
requestPayload: parsed.requestPayload,
|
||||||
|
requestContext: parsed.requestContext,
|
||||||
|
responsePayload: parsed.responsePayload,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const seconds = Math.min(Math.pow(2, attempt), 900);
|
||||||
|
console.log(
|
||||||
|
"delaying retry by ",
|
||||||
|
seconds,
|
||||||
|
"seconds for attempt",
|
||||||
|
attempt,
|
||||||
|
);
|
||||||
|
parsed.requestPayload.attempts = attempt;
|
||||||
|
await sqs.send(
|
||||||
|
new SendMessageCommand({
|
||||||
|
QueueUrl: Resource.RetryQueue.url,
|
||||||
|
DelaySeconds: seconds,
|
||||||
|
MessageBody: JSON.stringify({
|
||||||
|
requestPayload: parsed.requestPayload,
|
||||||
|
requestContext: parsed.requestContext,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parsed.responsePayload) {
|
||||||
|
console.log("triggering function");
|
||||||
|
try {
|
||||||
|
await lambda.send(
|
||||||
|
new InvokeCommand({
|
||||||
|
InvocationType: "Event",
|
||||||
|
Payload: Buffer.from(JSON.stringify(parsed.requestPayload)),
|
||||||
|
FunctionName: functionName,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof ResourceNotFoundException) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,18 @@
|
|||||||
"@bufbuild/protoc-gen-es": "^2.2.3"
|
"@bufbuild/protoc-gen-es": "^2.2.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bufbuild/protobuf": "^2.2.3"
|
"@bufbuild/protobuf": "^2.2.3",
|
||||||
|
"@chainsafe/libp2p-noise": "^16.1.3",
|
||||||
|
"@chainsafe/libp2p-yamux": "^7.0.1",
|
||||||
|
"@libp2p/identify": "^3.0.32",
|
||||||
|
"@libp2p/interface": "^2.10.2",
|
||||||
|
"@libp2p/ping": "^2.0.32",
|
||||||
|
"@libp2p/websockets": "^9.2.13",
|
||||||
|
"@multiformats/multiaddr": "^12.4.0",
|
||||||
|
"it-length-prefixed": "^10.0.1",
|
||||||
|
"it-pipe": "^3.0.1",
|
||||||
|
"libp2p": "^2.8.8",
|
||||||
|
"uint8arraylist": "^2.4.8",
|
||||||
|
"uint8arrays": "^5.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,37 +1,305 @@
|
|||||||
import { LatencyTracker } from "./latency";
|
import { LatencyTracker } from "./latency";
|
||||||
|
import { Uint8ArrayList } from "uint8arraylist";
|
||||||
|
import { allocUnsafe } from "uint8arrays/alloc";
|
||||||
|
import { pipe } from "it-pipe";
|
||||||
|
import { decode, encode } from "it-length-prefixed";
|
||||||
|
import { Stream } from "@libp2p/interface";
|
||||||
|
|
||||||
export interface MessageBase {
|
export interface MessageBase {
|
||||||
payload_type: string;
|
payload_type: string;
|
||||||
latency?: LatencyTracker;
|
latency?: LatencyTracker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MessageRaw extends MessageBase {
|
||||||
|
data: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NewMessageRaw(type: string, data: any): Uint8Array {
|
||||||
|
const msg = {
|
||||||
|
payload_type: type,
|
||||||
|
data: data,
|
||||||
|
};
|
||||||
|
return new TextEncoder().encode(JSON.stringify(msg));
|
||||||
|
}
|
||||||
|
|
||||||
export interface MessageICE extends MessageBase {
|
export interface MessageICE extends MessageBase {
|
||||||
payload_type: "ice";
|
|
||||||
candidate: RTCIceCandidateInit;
|
candidate: RTCIceCandidateInit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function NewMessageICE(
|
||||||
|
type: string,
|
||||||
|
candidate: RTCIceCandidateInit,
|
||||||
|
): Uint8Array {
|
||||||
|
const msg = {
|
||||||
|
payload_type: type,
|
||||||
|
candidate: candidate,
|
||||||
|
};
|
||||||
|
return new TextEncoder().encode(JSON.stringify(msg));
|
||||||
|
}
|
||||||
|
|
||||||
export interface MessageSDP extends MessageBase {
|
export interface MessageSDP extends MessageBase {
|
||||||
payload_type: "sdp";
|
|
||||||
sdp: RTCSessionDescriptionInit;
|
sdp: RTCSessionDescriptionInit;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum JoinerType {
|
export function NewMessageSDP(
|
||||||
JoinerNode = 0,
|
type: string,
|
||||||
JoinerClient = 1,
|
sdp: RTCSessionDescriptionInit,
|
||||||
|
): Uint8Array {
|
||||||
|
const msg = {
|
||||||
|
payload_type: type,
|
||||||
|
sdp: sdp,
|
||||||
|
};
|
||||||
|
return new TextEncoder().encode(JSON.stringify(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MessageJoin extends MessageBase {
|
const MAX_SIZE = 1024 * 1024; // 1MB
|
||||||
payload_type: "join";
|
const MAX_QUEUE_SIZE = 1000; // Maximum number of messages in the queue
|
||||||
joiner_type: JoinerType;
|
|
||||||
|
// Custom 4-byte length encoder
|
||||||
|
export const length4ByteEncoder = (length: number) => {
|
||||||
|
const buf = allocUnsafe(4);
|
||||||
|
|
||||||
|
// Write the length as a 32-bit unsigned integer (4 bytes)
|
||||||
|
buf[0] = length >>> 24;
|
||||||
|
buf[1] = (length >>> 16) & 0xff;
|
||||||
|
buf[2] = (length >>> 8) & 0xff;
|
||||||
|
buf[3] = length & 0xff;
|
||||||
|
|
||||||
|
// Set the bytes property to 4
|
||||||
|
length4ByteEncoder.bytes = 4;
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
};
|
||||||
|
length4ByteEncoder.bytes = 4;
|
||||||
|
|
||||||
|
// Custom 4-byte length decoder
|
||||||
|
export const length4ByteDecoder = (data: Uint8ArrayList) => {
|
||||||
|
if (data.byteLength < 4) {
|
||||||
|
// Not enough bytes to read the length
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AnswerType {
|
// Read the length from the first 4 bytes
|
||||||
AnswerOffline = 0,
|
let length = 0;
|
||||||
AnswerInUse,
|
length =
|
||||||
AnswerOK
|
(data.subarray(0, 1)[0] >>> 0) * 0x1000000 +
|
||||||
|
(data.subarray(1, 2)[0] >>> 0) * 0x10000 +
|
||||||
|
(data.subarray(2, 3)[0] >>> 0) * 0x100 +
|
||||||
|
(data.subarray(3, 4)[0] >>> 0);
|
||||||
|
|
||||||
|
// Set bytes read to 4
|
||||||
|
length4ByteDecoder.bytes = 4;
|
||||||
|
|
||||||
|
return length;
|
||||||
|
};
|
||||||
|
length4ByteDecoder.bytes = 4;
|
||||||
|
|
||||||
|
interface PromiseMessage {
|
||||||
|
data: Uint8Array;
|
||||||
|
resolve: () => void;
|
||||||
|
reject: (error: Error) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MessageAnswer extends MessageBase {
|
export class SafeStream {
|
||||||
payload_type: "answer";
|
private stream: Stream;
|
||||||
answer_type: AnswerType;
|
private callbacks: Map<string, ((data: any) => void)[]> = new Map();
|
||||||
|
private isReading: boolean = false;
|
||||||
|
private isWriting: boolean = false;
|
||||||
|
private closed: boolean = false;
|
||||||
|
private messageQueue: PromiseMessage[] = [];
|
||||||
|
private writeLock = false;
|
||||||
|
private readRetries = 0;
|
||||||
|
private writeRetries = 0;
|
||||||
|
private readonly MAX_RETRIES = 5;
|
||||||
|
|
||||||
|
constructor(stream: Stream) {
|
||||||
|
this.stream = stream;
|
||||||
|
this.startReading();
|
||||||
|
this.startWriting();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async startReading(): Promise<void> {
|
||||||
|
if (this.isReading || this.closed) return;
|
||||||
|
|
||||||
|
this.isReading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const source = this.stream.source;
|
||||||
|
const decodedSource = decode(source, {
|
||||||
|
maxDataLength: MAX_SIZE,
|
||||||
|
lengthDecoder: length4ByteDecoder,
|
||||||
|
});
|
||||||
|
|
||||||
|
for await (const chunk of decodedSource) {
|
||||||
|
if (this.closed) break;
|
||||||
|
|
||||||
|
this.readRetries = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = chunk.slice();
|
||||||
|
const message = JSON.parse(
|
||||||
|
new TextDecoder().decode(data),
|
||||||
|
) as MessageBase;
|
||||||
|
const msgType = message.payload_type;
|
||||||
|
|
||||||
|
if (this.callbacks.has(msgType)) {
|
||||||
|
const handlers = this.callbacks.get(msgType)!;
|
||||||
|
for (const handler of handlers) {
|
||||||
|
try {
|
||||||
|
handler(message);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error in message handler for ${msgType}:`, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error processing message:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Stream reading error:", err);
|
||||||
|
} finally {
|
||||||
|
this.isReading = false;
|
||||||
|
this.readRetries++;
|
||||||
|
|
||||||
|
// If not closed, try to restart reading
|
||||||
|
if (!this.closed && this.readRetries < this.MAX_RETRIES)
|
||||||
|
setTimeout(() => this.startReading(), 100);
|
||||||
|
else if (this.readRetries >= this.MAX_RETRIES)
|
||||||
|
console.error(
|
||||||
|
"Max retries reached for reading stream, stopping attempts",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerCallback(
|
||||||
|
msgType: string,
|
||||||
|
callback: (data: any) => void,
|
||||||
|
): void {
|
||||||
|
if (!this.callbacks.has(msgType)) {
|
||||||
|
this.callbacks.set(msgType, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.callbacks.get(msgType)!.push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeCallback(msgType: string, callback: (data: any) => void): void {
|
||||||
|
if (this.callbacks.has(msgType)) {
|
||||||
|
const callbacks = this.callbacks.get(msgType)!;
|
||||||
|
const index = callbacks.indexOf(callback);
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
callbacks.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callbacks.length === 0) {
|
||||||
|
this.callbacks.delete(msgType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async startWriting(): Promise<void> {
|
||||||
|
if (this.isWriting || this.closed) return;
|
||||||
|
|
||||||
|
this.isWriting = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create an async generator for real-time message processing
|
||||||
|
const messageSource = async function* (this: SafeStream) {
|
||||||
|
while (!this.closed) {
|
||||||
|
// Check if we have messages to send
|
||||||
|
if (this.messageQueue.length > 0) {
|
||||||
|
this.writeLock = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const message = this.messageQueue[0];
|
||||||
|
|
||||||
|
// Encode the message
|
||||||
|
const encoded = encode([message.data], {
|
||||||
|
maxDataLength: MAX_SIZE,
|
||||||
|
lengthEncoder: length4ByteEncoder,
|
||||||
|
});
|
||||||
|
|
||||||
|
for await (const chunk of encoded) {
|
||||||
|
yield chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove message after successful sending
|
||||||
|
this.writeRetries = 0;
|
||||||
|
const sentMessage = this.messageQueue.shift();
|
||||||
|
if (sentMessage)
|
||||||
|
sentMessage.resolve();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error encoding or sending message:", err);
|
||||||
|
const failedMessage = this.messageQueue.shift();
|
||||||
|
if (failedMessage)
|
||||||
|
failedMessage.reject(new Error(`Failed to send message: ${err}`));
|
||||||
|
} finally {
|
||||||
|
this.writeLock = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No messages to send, wait for a short period
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
await pipe(messageSource(), this.stream.sink).catch((err) => {
|
||||||
|
console.error("Sink error:", err);
|
||||||
|
this.isWriting = false;
|
||||||
|
this.writeRetries++;
|
||||||
|
|
||||||
|
// Try to restart if not closed
|
||||||
|
if (!this.closed && this.writeRetries < this.MAX_RETRIES) {
|
||||||
|
setTimeout(() => this.startWriting(), 1000);
|
||||||
|
} else if (this.writeRetries >= this.MAX_RETRIES) {
|
||||||
|
console.error("Max retries reached for writing to stream sink, stopping attempts");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Stream writing error:", err);
|
||||||
|
this.isWriting = false;
|
||||||
|
this.writeRetries++;
|
||||||
|
|
||||||
|
// Try to restart if not closed
|
||||||
|
if (!this.closed && this.writeRetries < this.MAX_RETRIES) {
|
||||||
|
setTimeout(() => this.startWriting(), 1000);
|
||||||
|
} else if (this.writeRetries >= this.MAX_RETRIES) {
|
||||||
|
console.error("Max retries reached for writing stream, stopping attempts");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async writeMessage(message: Uint8Array): Promise<void> {
|
||||||
|
if (this.closed) {
|
||||||
|
throw new Error("Cannot write to closed stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate message size before queuing
|
||||||
|
if (message.length > MAX_SIZE) {
|
||||||
|
throw new Error("Message size exceeds maximum size limit");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the message queue is too large
|
||||||
|
if (this.messageQueue.length >= MAX_QUEUE_SIZE) {
|
||||||
|
throw new Error("Message queue is full, cannot write message");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a promise to resolve when the message is sent
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.messageQueue.push({ data: message, resolve, reject } as PromiseMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public close(): void {
|
||||||
|
this.closed = true;
|
||||||
|
this.callbacks.clear();
|
||||||
|
// Reject pending messages
|
||||||
|
for (const msg of this.messageQueue)
|
||||||
|
msg.reject(new Error("Stream closed"));
|
||||||
|
|
||||||
|
this.messageQueue = [];
|
||||||
|
this.readRetries = 0;
|
||||||
|
this.writeRetries = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,27 @@
|
|||||||
import {
|
import {
|
||||||
MessageBase,
|
NewMessageRaw,
|
||||||
MessageICE,
|
NewMessageSDP,
|
||||||
MessageJoin,
|
NewMessageICE,
|
||||||
MessageSDP,
|
SafeStream,
|
||||||
MessageAnswer,
|
|
||||||
JoinerType,
|
|
||||||
AnswerType,
|
|
||||||
} from "./messages";
|
} from "./messages";
|
||||||
|
import { webSockets } from "@libp2p/websockets";
|
||||||
|
import { createLibp2p, Libp2p } from "libp2p";
|
||||||
|
import { noise } from "@chainsafe/libp2p-noise";
|
||||||
|
import { yamux } from "@chainsafe/libp2p-yamux";
|
||||||
|
import { identify } from "@libp2p/identify";
|
||||||
|
import { multiaddr } from "@multiformats/multiaddr";
|
||||||
|
import { Connection } from "@libp2p/interface";
|
||||||
|
import { ping } from "@libp2p/ping";
|
||||||
|
|
||||||
//FIXME: Sometimes the room will wait to say offline, then appear to be online after retrying :D
|
//FIXME: Sometimes the room will wait to say offline, then appear to be online after retrying :D
|
||||||
// This works for me, with my trashy internet, does it work for you as well?
|
// This works for me, with my trashy internet, does it work for you as well?
|
||||||
|
|
||||||
|
const NESTRI_PROTOCOL_STREAM_REQUEST = "/nestri-relay/stream-request/1.0.0";
|
||||||
|
|
||||||
export class WebRTCStream {
|
export class WebRTCStream {
|
||||||
private _ws: WebSocket | undefined = undefined;
|
private _p2p: Libp2p | undefined = undefined;
|
||||||
|
private _p2pConn: Connection | undefined = undefined;
|
||||||
|
private _p2pSafeStream: SafeStream | undefined = undefined;
|
||||||
private _pc: RTCPeerConnection | undefined = undefined;
|
private _pc: RTCPeerConnection | undefined = undefined;
|
||||||
private _audioTrack: MediaStreamTrack | undefined = undefined;
|
private _audioTrack: MediaStreamTrack | undefined = undefined;
|
||||||
private _videoTrack: MediaStreamTrack | undefined = undefined;
|
private _videoTrack: MediaStreamTrack | undefined = undefined;
|
||||||
@@ -24,7 +33,11 @@ export class WebRTCStream {
|
|||||||
private _isConnected: boolean = false; // Add flag to track connection state
|
private _isConnected: boolean = false; // Add flag to track connection state
|
||||||
currentFrameRate: number = 60;
|
currentFrameRate: number = 60;
|
||||||
|
|
||||||
constructor(serverURL: string, roomName: string, connectedCallback: (stream: MediaStream | null) => void) {
|
constructor(
|
||||||
|
serverURL: string,
|
||||||
|
roomName: string,
|
||||||
|
connectedCallback: (stream: MediaStream | null) => void,
|
||||||
|
) {
|
||||||
if (roomName.length <= 0) {
|
if (roomName.length <= 0) {
|
||||||
console.error("Room name not provided");
|
console.error("Room name not provided");
|
||||||
return;
|
return;
|
||||||
@@ -33,120 +46,114 @@ export class WebRTCStream {
|
|||||||
this._onConnected = connectedCallback;
|
this._onConnected = connectedCallback;
|
||||||
this._serverURL = serverURL;
|
this._serverURL = serverURL;
|
||||||
this._roomName = roomName;
|
this._roomName = roomName;
|
||||||
this._setup(serverURL, roomName);
|
this._setup(serverURL, roomName).catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setup(serverURL: string, roomName: string) {
|
private async _setup(serverURL: string, roomName: string) {
|
||||||
// Don't setup new connection if already connected
|
// Don't setup new connection if already connected
|
||||||
if (this._isConnected) {
|
if (this._isConnected) {
|
||||||
console.log("Already connected, skipping setup");
|
console.log("Already connected, skipping setup");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Setting up WebSocket");
|
console.log("Setting up libp2p");
|
||||||
const wsURL = serverURL.replace(/^http/, "ws");
|
|
||||||
this._ws = new WebSocket(`${wsURL}/api/ws/${roomName}`);
|
this._p2p = await createLibp2p({
|
||||||
this._ws.onopen = async () => {
|
transports: [webSockets()],
|
||||||
console.log("WebSocket opened");
|
connectionEncrypters: [noise()],
|
||||||
// Send join message
|
streamMuxers: [yamux()],
|
||||||
const joinMessage: MessageJoin = {
|
connectionGater: {
|
||||||
payload_type: "join",
|
denyDialMultiaddr: () => {
|
||||||
joiner_type: JoinerType.JoinerClient
|
return false;
|
||||||
};
|
},
|
||||||
this._ws!.send(JSON.stringify(joinMessage));
|
},
|
||||||
}
|
services: {
|
||||||
|
identify: identify(),
|
||||||
|
ping: ping(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this._p2p.addEventListener("peer:connect", async (e) => {
|
||||||
|
console.debug("Peer connected:", e.detail);
|
||||||
|
});
|
||||||
|
this._p2p.addEventListener("peer:disconnect", (e) => {
|
||||||
|
console.debug("Peer disconnected:", e.detail);
|
||||||
|
});
|
||||||
|
|
||||||
|
const ma = multiaddr(serverURL);
|
||||||
|
console.debug("Dialing peer at:", ma.toString());
|
||||||
|
this._p2pConn = await this._p2p.dial(ma);
|
||||||
|
|
||||||
|
if (this._p2pConn) {
|
||||||
|
console.log("Stream is being established");
|
||||||
|
let stream = await this._p2pConn
|
||||||
|
.newStream(NESTRI_PROTOCOL_STREAM_REQUEST)
|
||||||
|
.catch(console.error);
|
||||||
|
if (stream) {
|
||||||
|
this._p2pSafeStream = new SafeStream(stream);
|
||||||
|
console.log("Stream opened with peer");
|
||||||
|
|
||||||
let iceHolder: RTCIceCandidateInit[] = [];
|
let iceHolder: RTCIceCandidateInit[] = [];
|
||||||
|
this._p2pSafeStream.registerCallback("ice-candidate", (data) => {
|
||||||
|
if (this._pc) {
|
||||||
|
if (this._pc.remoteDescription) {
|
||||||
|
this._pc.addIceCandidate(data.candidate).catch((err) => {
|
||||||
|
console.error("Error adding ICE candidate:", err);
|
||||||
|
});
|
||||||
|
// Add held candidates
|
||||||
|
iceHolder.forEach((candidate) => {
|
||||||
|
this._pc!.addIceCandidate(candidate).catch((err) => {
|
||||||
|
console.error("Error adding held ICE candidate:", err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
iceHolder = [];
|
||||||
|
} else {
|
||||||
|
iceHolder.push(data.candidate);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
iceHolder.push(data.candidate);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this._ws.onmessage = async (e) => {
|
this._p2pSafeStream.registerCallback("offer", async (data) => {
|
||||||
// allow only JSON
|
|
||||||
if (typeof e.data === "object") return;
|
|
||||||
if (!e.data) return;
|
|
||||||
const message = JSON.parse(e.data) as MessageBase;
|
|
||||||
switch (message.payload_type) {
|
|
||||||
case "sdp":
|
|
||||||
if (!this._pc) {
|
if (!this._pc) {
|
||||||
// Setup peer connection now
|
// Setup peer connection now
|
||||||
this._setupPeerConnection();
|
this._setupPeerConnection();
|
||||||
}
|
}
|
||||||
console.log("Received SDP: ", (message as MessageSDP).sdp);
|
await this._pc!.setRemoteDescription(data.sdp);
|
||||||
await this._pc!.setRemoteDescription((message as MessageSDP).sdp);
|
|
||||||
// Create our answer
|
// Create our answer
|
||||||
const answer = await this._pc!.createAnswer();
|
const answer = await this._pc!.createAnswer();
|
||||||
// Force stereo in Chromium browsers
|
// Force stereo in Chromium browsers
|
||||||
answer.sdp = this.forceOpusStereo(answer.sdp!);
|
answer.sdp = this.forceOpusStereo(answer.sdp!);
|
||||||
await this._pc!.setLocalDescription(answer);
|
await this._pc!.setLocalDescription(answer);
|
||||||
this._ws!.send(JSON.stringify({
|
// Send answer back
|
||||||
payload_type: "sdp",
|
const answerMsg = NewMessageSDP("answer", answer);
|
||||||
sdp: answer
|
await this._p2pSafeStream?.writeMessage(answerMsg);
|
||||||
}));
|
});
|
||||||
break;
|
|
||||||
case "ice":
|
|
||||||
if (!this._pc) break;
|
|
||||||
if (this._pc.remoteDescription) {
|
|
||||||
try {
|
|
||||||
await this._pc.addIceCandidate((message as MessageICE).candidate);
|
|
||||||
// Add held ICE candidates
|
|
||||||
for (const ice of iceHolder) {
|
|
||||||
try {
|
|
||||||
await this._pc.addIceCandidate(ice);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Error adding held ICE candidate: ", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
iceHolder = [];
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Error adding ICE candidate: ", e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
iceHolder.push((message as MessageICE).candidate);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "answer":
|
|
||||||
switch ((message as MessageAnswer).answer_type) {
|
|
||||||
case AnswerType.AnswerOffline:
|
|
||||||
console.log("Room is offline");
|
|
||||||
// Call callback with null stream
|
|
||||||
if (this._onConnected)
|
|
||||||
this._onConnected(null);
|
|
||||||
|
|
||||||
break;
|
this._p2pSafeStream.registerCallback("request-stream-offline", (data) => {
|
||||||
case AnswerType.AnswerInUse:
|
console.warn("Stream is offline for room:", data.roomName);
|
||||||
console.warn("Room is in use, we shouldn't even be getting this message");
|
this._onConnected?.(null);
|
||||||
break;
|
});
|
||||||
case AnswerType.AnswerOK:
|
|
||||||
console.log("Joining Room was successful");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.error("Unknown message type: ", message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._ws.onclose = () => {
|
// Send stream request
|
||||||
console.log("WebSocket closed, reconnecting in 3 seconds");
|
// marshal room name into json
|
||||||
if (this._onConnected)
|
const request = NewMessageRaw(
|
||||||
this._onConnected(null);
|
"request-stream-room",
|
||||||
|
roomName,
|
||||||
// Clear PeerConnection
|
);
|
||||||
this._cleanupPeerConnection()
|
await this._p2pSafeStream.writeMessage(request);
|
||||||
|
|
||||||
this._handleConnectionFailure()
|
|
||||||
// setTimeout(() => {
|
|
||||||
// this._setup(serverURL, roomName);
|
|
||||||
// }, this._connectionTimeout);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._ws.onerror = (e) => {
|
|
||||||
console.error("WebSocket error: ", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forces opus to stereo in Chromium browsers, because of course
|
// Forces opus to stereo in Chromium browsers, because of course
|
||||||
private forceOpusStereo(SDP: string): string {
|
private forceOpusStereo(SDP: string): string {
|
||||||
// Look for "minptime=10;useinbandfec=1" and replace with "minptime=10;useinbandfec=1;stereo=1;sprop-stereo=1;"
|
// Look for "minptime=10;useinbandfec=1" and replace with "minptime=10;useinbandfec=1;stereo=1;sprop-stereo=1;"
|
||||||
return SDP.replace(/(minptime=10;useinbandfec=1)/, "$1;stereo=1;sprop-stereo=1;");
|
return SDP.replace(
|
||||||
|
/(minptime=10;useinbandfec=1)/,
|
||||||
|
"$1;stereo=1;sprop-stereo=1;",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setupPeerConnection() {
|
private _setupPeerConnection() {
|
||||||
@@ -158,43 +165,50 @@ export class WebRTCStream {
|
|||||||
this._pc = new RTCPeerConnection({
|
this._pc = new RTCPeerConnection({
|
||||||
iceServers: [
|
iceServers: [
|
||||||
{
|
{
|
||||||
urls: "stun:stun.l.google.com:19302"
|
urls: "stun:stun.l.google.com:19302",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
this._pc.ontrack = (e) => {
|
this._pc.ontrack = (e) => {
|
||||||
console.log("Track received: ", e.track);
|
console.debug("Track received: ", e.track);
|
||||||
if (e.track.kind === "audio")
|
if (e.track.kind === "audio") this._audioTrack = e.track;
|
||||||
this._audioTrack = e.track;
|
else if (e.track.kind === "video") this._videoTrack = e.track;
|
||||||
else if (e.track.kind === "video")
|
|
||||||
this._videoTrack = e.track;
|
|
||||||
|
|
||||||
this._checkConnectionState();
|
this._checkConnectionState();
|
||||||
};
|
};
|
||||||
|
|
||||||
this._pc.onconnectionstatechange = () => {
|
this._pc.onconnectionstatechange = () => {
|
||||||
console.log("Connection state changed to: ", this._pc!.connectionState);
|
console.debug("Connection state changed to: ", this._pc!.connectionState);
|
||||||
this._checkConnectionState();
|
this._checkConnectionState();
|
||||||
};
|
};
|
||||||
|
|
||||||
this._pc.oniceconnectionstatechange = () => {
|
this._pc.oniceconnectionstatechange = () => {
|
||||||
console.log("ICE connection state changed to: ", this._pc!.iceConnectionState);
|
console.debug(
|
||||||
|
"ICE connection state changed to: ",
|
||||||
|
this._pc!.iceConnectionState,
|
||||||
|
);
|
||||||
this._checkConnectionState();
|
this._checkConnectionState();
|
||||||
};
|
};
|
||||||
|
|
||||||
this._pc.onicegatheringstatechange = () => {
|
this._pc.onicegatheringstatechange = () => {
|
||||||
console.log("ICE gathering state changed to: ", this._pc!.iceGatheringState);
|
console.debug(
|
||||||
|
"ICE gathering state changed to: ",
|
||||||
|
this._pc!.iceGatheringState,
|
||||||
|
);
|
||||||
this._checkConnectionState();
|
this._checkConnectionState();
|
||||||
};
|
};
|
||||||
|
|
||||||
this._pc.onicecandidate = (e) => {
|
this._pc.onicecandidate = (e) => {
|
||||||
if (e.candidate) {
|
if (e.candidate) {
|
||||||
const message: MessageICE = {
|
const iceMsg = NewMessageICE("ice-candidate", e.candidate);
|
||||||
payload_type: "ice",
|
if (this._p2pSafeStream) {
|
||||||
candidate: e.candidate
|
this._p2pSafeStream.writeMessage(iceMsg).catch((err) =>
|
||||||
};
|
console.error("Error sending ICE candidate:", err),
|
||||||
this._ws!.send(JSON.stringify(message));
|
);
|
||||||
|
} else {
|
||||||
|
console.warn("P2P stream not established, cannot send ICE candidate");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -207,26 +221,35 @@ export class WebRTCStream {
|
|||||||
private _checkConnectionState() {
|
private _checkConnectionState() {
|
||||||
if (!this._pc) return;
|
if (!this._pc) return;
|
||||||
|
|
||||||
console.log("Checking connection state:", {
|
console.debug("Checking connection state:", {
|
||||||
connectionState: this._pc.connectionState,
|
connectionState: this._pc.connectionState,
|
||||||
iceConnectionState: this._pc.iceConnectionState,
|
iceConnectionState: this._pc.iceConnectionState,
|
||||||
hasAudioTrack: !!this._audioTrack,
|
hasAudioTrack: !!this._audioTrack,
|
||||||
hasVideoTrack: !!this._videoTrack,
|
hasVideoTrack: !!this._videoTrack,
|
||||||
isConnected: this._isConnected
|
isConnected: this._isConnected,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this._pc.connectionState === "connected" && this._audioTrack !== undefined && this._videoTrack !== undefined) {
|
if (
|
||||||
|
this._pc.connectionState === "connected" &&
|
||||||
|
this._audioTrack !== undefined &&
|
||||||
|
this._videoTrack !== undefined
|
||||||
|
) {
|
||||||
this._clearConnectionTimer();
|
this._clearConnectionTimer();
|
||||||
if (!this._isConnected) {
|
if (!this._isConnected) {
|
||||||
// Only trigger callback if not already connected
|
// Only trigger callback if not already connected
|
||||||
this._isConnected = true;
|
this._isConnected = true;
|
||||||
if (this._onConnected !== undefined) {
|
if (this._onConnected !== undefined) {
|
||||||
this._onConnected(new MediaStream([this._audioTrack, this._videoTrack]));
|
this._onConnected(
|
||||||
|
new MediaStream([this._audioTrack, this._videoTrack]),
|
||||||
|
);
|
||||||
|
|
||||||
// Continuously set low-latency target
|
// Continuously set low-latency target
|
||||||
this._pc.getReceivers().forEach((receiver: RTCRtpReceiver) => {
|
this._pc.getReceivers().forEach((receiver: RTCRtpReceiver) => {
|
||||||
let intervalLoop = setInterval(async () => {
|
let intervalLoop = setInterval(async () => {
|
||||||
if (receiver.track.readyState !== "live" || (receiver.transport && receiver.transport.state !== "connected")) {
|
if (
|
||||||
|
receiver.track.readyState !== "live" ||
|
||||||
|
(receiver.transport && receiver.transport.state !== "connected")
|
||||||
|
) {
|
||||||
clearInterval(intervalLoop);
|
clearInterval(intervalLoop);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@@ -239,9 +262,11 @@ export class WebRTCStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._gatherFrameRate();
|
this._gatherFrameRate();
|
||||||
} else if (this._pc.connectionState === "failed" ||
|
} else if (
|
||||||
|
this._pc.connectionState === "failed" ||
|
||||||
this._pc.connectionState === "closed" ||
|
this._pc.connectionState === "closed" ||
|
||||||
this._pc.iceConnectionState === "failed") {
|
this._pc.iceConnectionState === "failed"
|
||||||
|
) {
|
||||||
console.log("Connection failed or closed, attempting reconnect");
|
console.log("Connection failed or closed, attempting reconnect");
|
||||||
this._isConnected = false; // Reset connected state
|
this._isConnected = false; // Reset connected state
|
||||||
this._handleConnectionFailure();
|
this._handleConnectionFailure();
|
||||||
@@ -250,7 +275,8 @@ export class WebRTCStream {
|
|||||||
|
|
||||||
private _handleConnectionFailure() {
|
private _handleConnectionFailure() {
|
||||||
this._clearConnectionTimer();
|
this._clearConnectionTimer();
|
||||||
if (this._isConnected) { // Only notify if previously connected
|
if (this._isConnected) {
|
||||||
|
// Only notify if previously connected
|
||||||
this._isConnected = false;
|
this._isConnected = false;
|
||||||
if (this._onConnected) {
|
if (this._onConnected) {
|
||||||
this._onConnected(null);
|
this._onConnected(null);
|
||||||
@@ -260,7 +286,7 @@ export class WebRTCStream {
|
|||||||
|
|
||||||
// Attempt to reconnect only if not already connected
|
// Attempt to reconnect only if not already connected
|
||||||
if (!this._isConnected && this._serverURL && this._roomName) {
|
if (!this._isConnected && this._serverURL && this._roomName) {
|
||||||
this._setup(this._serverURL, this._roomName);
|
this._setup(this._serverURL, this._roomName).catch((err) => console.error("Reconnection failed:", err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,10 +302,8 @@ export class WebRTCStream {
|
|||||||
|
|
||||||
if (this._audioTrack || this._videoTrack) {
|
if (this._audioTrack || this._videoTrack) {
|
||||||
try {
|
try {
|
||||||
if (this._audioTrack)
|
if (this._audioTrack) this._audioTrack.stop();
|
||||||
this._audioTrack.stop();
|
if (this._videoTrack) this._videoTrack.stop();
|
||||||
if (this._videoTrack)
|
|
||||||
this._videoTrack.stop();
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error stopping media tracks:", err);
|
console.error("Error stopping media tracks:", err);
|
||||||
}
|
}
|
||||||
@@ -308,14 +332,16 @@ export class WebRTCStream {
|
|||||||
private _setupDataChannelEvents() {
|
private _setupDataChannelEvents() {
|
||||||
if (!this._dataChannel) return;
|
if (!this._dataChannel) return;
|
||||||
|
|
||||||
this._dataChannel.onclose = () => console.log('sendChannel has closed')
|
this._dataChannel.onclose = () => console.log("sendChannel has closed");
|
||||||
this._dataChannel.onopen = () => console.log('sendChannel has opened')
|
this._dataChannel.onopen = () => console.log("sendChannel has opened");
|
||||||
this._dataChannel.onmessage = e => console.log(`Message from DataChannel '${this._dataChannel?.label}' payload '${e.data}'`)
|
this._dataChannel.onmessage = (e) =>
|
||||||
|
console.log(
|
||||||
|
`Message from DataChannel '${this._dataChannel?.label}' payload '${e.data}'`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _gatherFrameRate() {
|
private _gatherFrameRate() {
|
||||||
if (this._pc === undefined || this._videoTrack === undefined)
|
if (this._pc === undefined || this._videoTrack === undefined) return;
|
||||||
return;
|
|
||||||
|
|
||||||
const videoInfoPromise = new Promise<{ fps: number }>((resolve) => {
|
const videoInfoPromise = new Promise<{ fps: number }>((resolve) => {
|
||||||
// Keep trying to get fps until it's found
|
// Keep trying to get fps until it's found
|
||||||
@@ -337,24 +363,25 @@ export class WebRTCStream {
|
|||||||
});
|
});
|
||||||
|
|
||||||
videoInfoPromise.then((value) => {
|
videoInfoPromise.then((value) => {
|
||||||
this.currentFrameRate = value.fps
|
this.currentFrameRate = value.fps;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send binary message through the data channel
|
// Send binary message through the data channel
|
||||||
public sendBinary(data: Uint8Array) {
|
public sendBinary(data: Uint8Array) {
|
||||||
if (this._dataChannel && this._dataChannel.readyState === "open")
|
if (this._dataChannel && this._dataChannel.readyState === "open")
|
||||||
this._dataChannel.send(data);
|
this._dataChannel.send(data);
|
||||||
else
|
else console.log("Data channel not open or not established.");
|
||||||
console.log("Data channel not open or not established.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public disconnect() {
|
public disconnect() {
|
||||||
this._clearConnectionTimer();
|
this._clearConnectionTimer();
|
||||||
this._cleanupPeerConnection();
|
this._cleanupPeerConnection();
|
||||||
if (this._ws) {
|
if (this._p2pConn) {
|
||||||
this._ws.close();
|
this._p2pConn
|
||||||
this._ws = undefined;
|
.close()
|
||||||
|
.catch((err) => console.error("Error closing P2P connection:", err));
|
||||||
|
this._p2pConn = undefined;
|
||||||
}
|
}
|
||||||
this._isConnected = false;
|
this._isConnected = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ type Resource struct {
|
|||||||
Auth struct {
|
Auth struct {
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
}
|
}
|
||||||
AuthFingerprintKey struct {
|
/*AuthFingerprintKey struct {
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
}
|
}*/
|
||||||
Realtime struct {
|
Realtime struct {
|
||||||
Endpoint string `json:"endpoint"`
|
Endpoint string `json:"endpoint"`
|
||||||
Authorizer string `json:"authorizer"`
|
Authorizer string `json:"authorizer"`
|
||||||
|
|||||||
1
packages/relay/.gitignore
vendored
Normal file
1
packages/relay/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
persist-data/
|
||||||
@@ -3,16 +3,15 @@ module relay
|
|||||||
go 1.24
|
go 1.24
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gorilla/websocket v1.5.3
|
|
||||||
github.com/libp2p/go-libp2p v0.41.1
|
github.com/libp2p/go-libp2p v0.41.1
|
||||||
github.com/libp2p/go-libp2p-pubsub v0.13.1
|
github.com/libp2p/go-libp2p-pubsub v0.13.1
|
||||||
github.com/libp2p/go-reuseport v0.4.0
|
github.com/libp2p/go-reuseport v0.4.0
|
||||||
github.com/multiformats/go-multiaddr v0.15.0
|
github.com/multiformats/go-multiaddr v0.15.0
|
||||||
github.com/oklog/ulid/v2 v2.1.0
|
github.com/oklog/ulid/v2 v2.1.1
|
||||||
github.com/pion/ice/v4 v4.0.9
|
github.com/pion/ice/v4 v4.0.10
|
||||||
github.com/pion/interceptor v0.1.37
|
github.com/pion/interceptor v0.1.38
|
||||||
github.com/pion/rtp v1.8.13
|
github.com/pion/rtp v1.8.15
|
||||||
github.com/pion/webrtc/v4 v4.0.14
|
github.com/pion/webrtc/v4 v4.1.1
|
||||||
google.golang.org/protobuf v1.36.6
|
google.golang.org/protobuf v1.36.6
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,32 +25,35 @@ require (
|
|||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
github.com/elastic/gosigar v0.14.3 // indirect
|
github.com/elastic/gosigar v0.14.3 // indirect
|
||||||
|
github.com/filecoin-project/go-clock v0.1.0 // indirect
|
||||||
github.com/flynn/noise v1.1.0 // indirect
|
github.com/flynn/noise v1.1.0 // indirect
|
||||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/google/gopacket v1.1.19 // indirect
|
github.com/google/gopacket v1.1.19 // indirect
|
||||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
github.com/huin/goupnp v1.3.0 // indirect
|
github.com/huin/goupnp v1.3.0 // indirect
|
||||||
github.com/ipfs/go-cid v0.5.0 // indirect
|
github.com/ipfs/go-cid v0.5.0 // indirect
|
||||||
github.com/ipfs/go-log/v2 v2.5.1 // indirect
|
github.com/ipfs/go-log/v2 v2.6.0 // indirect
|
||||||
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
|
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
|
||||||
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
|
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||||
github.com/koron/go-ssdp v0.0.5 // indirect
|
github.com/koron/go-ssdp v0.0.6 // indirect
|
||||||
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
|
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
|
||||||
github.com/libp2p/go-flow-metrics v0.2.0 // indirect
|
github.com/libp2p/go-flow-metrics v0.3.0 // indirect
|
||||||
github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect
|
github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect
|
||||||
github.com/libp2p/go-msgio v0.3.0 // indirect
|
github.com/libp2p/go-msgio v0.3.0 // indirect
|
||||||
github.com/libp2p/go-netroute v0.2.2 // indirect
|
github.com/libp2p/go-netroute v0.2.2 // indirect
|
||||||
github.com/libp2p/go-yamux/v5 v5.0.0 // indirect
|
github.com/libp2p/go-yamux/v5 v5.0.0 // indirect
|
||||||
|
github.com/libp2p/zeroconf/v2 v2.2.0 // indirect
|
||||||
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect
|
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/miekg/dns v1.1.64 // indirect
|
github.com/miekg/dns v1.1.66 // indirect
|
||||||
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
|
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
|
||||||
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
|
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
|
||||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||||
@@ -66,7 +68,7 @@ require (
|
|||||||
github.com/multiformats/go-multistream v0.6.0 // indirect
|
github.com/multiformats/go-multistream v0.6.0 // indirect
|
||||||
github.com/multiformats/go-varint v0.0.7 // indirect
|
github.com/multiformats/go-varint v0.0.7 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.23.3 // indirect
|
github.com/onsi/ginkgo/v2 v2.23.4 // indirect
|
||||||
github.com/opencontainers/runtime-spec v1.2.1 // indirect
|
github.com/opencontainers/runtime-spec v1.2.1 // indirect
|
||||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||||
github.com/pion/datachannel v1.5.10 // indirect
|
github.com/pion/datachannel v1.5.10 // indirect
|
||||||
@@ -76,37 +78,39 @@ require (
|
|||||||
github.com/pion/mdns/v2 v2.0.7 // indirect
|
github.com/pion/mdns/v2 v2.0.7 // indirect
|
||||||
github.com/pion/randutil v0.1.0 // indirect
|
github.com/pion/randutil v0.1.0 // indirect
|
||||||
github.com/pion/rtcp v1.2.15 // indirect
|
github.com/pion/rtcp v1.2.15 // indirect
|
||||||
github.com/pion/sctp v1.8.37 // indirect
|
github.com/pion/sctp v1.8.39 // indirect
|
||||||
github.com/pion/sdp/v3 v3.0.11 // indirect
|
github.com/pion/sdp/v3 v3.0.13 // indirect
|
||||||
github.com/pion/srtp/v3 v3.0.4 // indirect
|
github.com/pion/srtp/v3 v3.0.4 // indirect
|
||||||
github.com/pion/stun v0.6.1 // indirect
|
github.com/pion/stun v0.6.1 // indirect
|
||||||
github.com/pion/stun/v3 v3.0.0 // indirect
|
github.com/pion/stun/v3 v3.0.0 // indirect
|
||||||
github.com/pion/transport/v2 v2.2.10 // indirect
|
github.com/pion/transport/v2 v2.2.10 // indirect
|
||||||
github.com/pion/transport/v3 v3.0.7 // indirect
|
github.com/pion/transport/v3 v3.0.7 // indirect
|
||||||
github.com/pion/turn/v4 v4.0.0 // indirect
|
github.com/pion/turn/v4 v4.0.2 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/prometheus/client_golang v1.21.1 // indirect
|
github.com/prometheus/client_golang v1.22.0 // indirect
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
github.com/prometheus/common v0.63.0 // indirect
|
github.com/prometheus/common v0.64.0 // indirect
|
||||||
github.com/prometheus/procfs v0.16.0 // indirect
|
github.com/prometheus/procfs v0.16.1 // indirect
|
||||||
github.com/quic-go/qpack v0.5.1 // indirect
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
github.com/quic-go/quic-go v0.50.1 // indirect
|
github.com/quic-go/quic-go v0.52.0 // indirect
|
||||||
github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 // indirect
|
github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 // indirect
|
||||||
github.com/raulk/go-watchdog v1.3.0 // indirect
|
github.com/raulk/go-watchdog v1.3.0 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
github.com/wlynxg/anet v0.0.5 // indirect
|
github.com/wlynxg/anet v0.0.5 // indirect
|
||||||
go.uber.org/dig v1.18.1 // indirect
|
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||||
go.uber.org/fx v1.23.0 // indirect
|
go.uber.org/dig v1.19.0 // indirect
|
||||||
go.uber.org/mock v0.5.0 // indirect
|
go.uber.org/fx v1.24.0 // indirect
|
||||||
|
go.uber.org/mock v0.5.2 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.uber.org/zap v1.27.0 // indirect
|
go.uber.org/zap v1.27.0 // indirect
|
||||||
golang.org/x/crypto v0.36.0 // indirect
|
golang.org/x/crypto v0.38.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
|
||||||
golang.org/x/mod v0.24.0 // indirect
|
golang.org/x/mod v0.24.0 // indirect
|
||||||
golang.org/x/net v0.38.0 // indirect
|
golang.org/x/net v0.40.0 // indirect
|
||||||
golang.org/x/sync v0.13.0 // indirect
|
golang.org/x/sync v0.14.0 // indirect
|
||||||
golang.org/x/sys v0.32.0 // indirect
|
golang.org/x/sys v0.33.0 // indirect
|
||||||
golang.org/x/text v0.24.0 // indirect
|
golang.org/x/text v0.25.0 // indirect
|
||||||
golang.org/x/tools v0.31.0 // indirect
|
golang.org/x/tools v0.33.0 // indirect
|
||||||
lukechampine.com/blake3 v1.4.0 // indirect
|
lukechampine.com/blake3 v1.4.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D
|
|||||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
|
||||||
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||||
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
|
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
|
||||||
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||||
@@ -47,6 +46,8 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn
|
|||||||
github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
|
github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
|
||||||
github.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/uo=
|
github.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/uo=
|
||||||
github.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
|
github.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
|
||||||
|
github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9gd6MPfXbKVU=
|
||||||
|
github.com/filecoin-project/go-clock v0.1.0/go.mod h1:4uB/O4PvOjlx1VCMdZ9MyDZXRm//gkj1ELEbxfI1AZs=
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg=
|
github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg=
|
||||||
github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
|
github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
|
||||||
@@ -85,8 +86,8 @@ github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF
|
|||||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a h1:rDA3FfmxwXR+BVKKdz55WwMJ1pD2hJQNW31d+l3mPk4=
|
||||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||||
@@ -102,8 +103,8 @@ github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
|
|||||||
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
|
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
|
||||||
github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg=
|
github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg=
|
||||||
github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk=
|
github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk=
|
||||||
github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY=
|
github.com/ipfs/go-log/v2 v2.6.0 h1:2Nu1KKQQ2ayonKp4MPo6pXCjqw1ULc9iohRqWV5EYqg=
|
||||||
github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI=
|
github.com/ipfs/go-log/v2 v2.6.0/go.mod h1:p+Efr3qaY5YXpx9TX7MoLCSEZX5boSWj9wh86P5HJa8=
|
||||||
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
|
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
|
||||||
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
||||||
github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk=
|
github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk=
|
||||||
@@ -118,8 +119,8 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt
|
|||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/koron/go-ssdp v0.0.5 h1:E1iSMxIs4WqxTbIBLtmNBeOOC+1sCIXQeqTWVnpmwhk=
|
github.com/koron/go-ssdp v0.0.6 h1:Jb0h04599eq/CY7rB5YEqPS83HmRfHP2azkxMN2rFtU=
|
||||||
github.com/koron/go-ssdp v0.0.5/go.mod h1:Qm59B7hpKpDqfyRNWRNr00jGwLdXjDyZh6y7rH6VS0w=
|
github.com/koron/go-ssdp v0.0.6/go.mod h1:0R9LfRJGek1zWTjN3JUNlm5INCDYGpRDfAptnct63fI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
@@ -131,8 +132,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
|
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
|
||||||
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
|
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
|
||||||
github.com/libp2p/go-flow-metrics v0.2.0 h1:EIZzjmeOE6c8Dav0sNv35vhZxATIXWZg6j/C08XmmDw=
|
github.com/libp2p/go-flow-metrics v0.3.0 h1:q31zcHUvHnwDO0SHaukewPYgwOBSxtt830uJtUx6784=
|
||||||
github.com/libp2p/go-flow-metrics v0.2.0/go.mod h1:st3qqfu8+pMfh+9Mzqb2GTiwrAGjIPszEjZmtksN8Jc=
|
github.com/libp2p/go-flow-metrics v0.3.0/go.mod h1:nuhlreIwEguM1IvHAew3ij7A8BMlyHQJ279ao24eZZo=
|
||||||
github.com/libp2p/go-libp2p v0.41.1 h1:8ecNQVT5ev/jqALTvisSJeVNvXYJyK4NhQx1nNRXQZE=
|
github.com/libp2p/go-libp2p v0.41.1 h1:8ecNQVT5ev/jqALTvisSJeVNvXYJyK4NhQx1nNRXQZE=
|
||||||
github.com/libp2p/go-libp2p v0.41.1/go.mod h1:DcGTovJzQl/I7HMrby5ZRjeD0kQkGiy+9w6aEkSZpRI=
|
github.com/libp2p/go-libp2p v0.41.1/go.mod h1:DcGTovJzQl/I7HMrby5ZRjeD0kQkGiy+9w6aEkSZpRI=
|
||||||
github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94=
|
github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94=
|
||||||
@@ -149,17 +150,19 @@ github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQsc
|
|||||||
github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU=
|
github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU=
|
||||||
github.com/libp2p/go-yamux/v5 v5.0.0 h1:2djUh96d3Jiac/JpGkKs4TO49YhsfLopAoryfPmf+Po=
|
github.com/libp2p/go-yamux/v5 v5.0.0 h1:2djUh96d3Jiac/JpGkKs4TO49YhsfLopAoryfPmf+Po=
|
||||||
github.com/libp2p/go-yamux/v5 v5.0.0/go.mod h1:en+3cdX51U0ZslwRdRLrvQsdayFt3TSUKvBGErzpWbU=
|
github.com/libp2p/go-yamux/v5 v5.0.0/go.mod h1:en+3cdX51U0ZslwRdRLrvQsdayFt3TSUKvBGErzpWbU=
|
||||||
|
github.com/libp2p/zeroconf/v2 v2.2.0 h1:Cup06Jv6u81HLhIj1KasuNM/RHHrJ8T7wOTS4+Tv53Q=
|
||||||
|
github.com/libp2p/zeroconf/v2 v2.2.0/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs=
|
||||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk=
|
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk=
|
||||||
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU=
|
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||||
github.com/miekg/dns v1.1.64 h1:wuZgD9wwCE6XMT05UU/mlSko71eRSXEAm2EbjQXLKnQ=
|
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||||
github.com/miekg/dns v1.1.64/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck=
|
github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE=
|
||||||
|
github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE=
|
||||||
github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8=
|
github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8=
|
||||||
github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms=
|
github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms=
|
||||||
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc=
|
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc=
|
||||||
@@ -201,12 +204,12 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
|||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||||
github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
|
github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
|
||||||
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
|
github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
|
||||||
github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0=
|
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
|
||||||
github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
|
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
|
||||||
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
|
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
|
||||||
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
|
github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||||
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||||
github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww=
|
github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww=
|
||||||
github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||||
@@ -221,10 +224,10 @@ github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk=
|
|||||||
github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=
|
github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=
|
||||||
github.com/pion/dtls/v3 v3.0.6 h1:7Hkd8WhAJNbRgq9RgdNh1aaWlZlGpYTzdqjy9x9sK2E=
|
github.com/pion/dtls/v3 v3.0.6 h1:7Hkd8WhAJNbRgq9RgdNh1aaWlZlGpYTzdqjy9x9sK2E=
|
||||||
github.com/pion/dtls/v3 v3.0.6/go.mod h1:iJxNQ3Uhn1NZWOMWlLxEEHAN5yX7GyPvvKw04v9bzYU=
|
github.com/pion/dtls/v3 v3.0.6/go.mod h1:iJxNQ3Uhn1NZWOMWlLxEEHAN5yX7GyPvvKw04v9bzYU=
|
||||||
github.com/pion/ice/v4 v4.0.9 h1:VKgU4MwA2LUDVLq+WBkpEHTcAb8c5iCvFMECeuPOZNk=
|
github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4=
|
||||||
github.com/pion/ice/v4 v4.0.9/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
|
github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
|
||||||
github.com/pion/interceptor v0.1.37 h1:aRA8Zpab/wE7/c0O3fh1PqY0AJI3fCSEM5lRWJVorwI=
|
github.com/pion/interceptor v0.1.38 h1:Mgt3XIIq47uR5vcLLahfRucE6tFPjxHak+z5ZZFEzLU=
|
||||||
github.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y=
|
github.com/pion/interceptor v0.1.38/go.mod h1:HS9X+Ue5LDE6q2C2tuvOuO83XkBdJFgn6MBDtfoJX4Q=
|
||||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||||
github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI=
|
github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI=
|
||||||
github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90=
|
github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90=
|
||||||
@@ -234,12 +237,12 @@ github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
|||||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||||
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
|
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
|
||||||
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
|
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
|
||||||
github.com/pion/rtp v1.8.13 h1:8uSUPpjSL4OlwZI8Ygqu7+h2p9NPFB+yAZ461Xn5sNg=
|
github.com/pion/rtp v1.8.15 h1:MuhuGn1cxpVCPLNY1lI7F1tQ8Spntpgf12ob+pOYT8s=
|
||||||
github.com/pion/rtp v1.8.13/go.mod h1:8uMBJj32Pa1wwx8Fuv/AsFhn8jsgw+3rUC2PfoBZ8p4=
|
github.com/pion/rtp v1.8.15/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=
|
||||||
github.com/pion/sctp v1.8.37 h1:ZDmGPtRPX9mKCiVXtMbTWybFw3z/hVKAZgU81wcOrqs=
|
github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE=
|
||||||
github.com/pion/sctp v1.8.37/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
|
github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
|
||||||
github.com/pion/sdp/v3 v3.0.11 h1:VhgVSopdsBKwhCFoyyPmT1fKMeV9nLMrEKxNOdy3IVI=
|
github.com/pion/sdp/v3 v3.0.13 h1:uN3SS2b+QDZnWXgdr69SM8KB4EbcnPnPf2Laxhty/l4=
|
||||||
github.com/pion/sdp/v3 v3.0.11/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
|
github.com/pion/sdp/v3 v3.0.13/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
|
||||||
github.com/pion/srtp/v3 v3.0.4 h1:2Z6vDVxzrX3UHEgrUyIGM4rRouoC7v+NiF1IHtp9B5M=
|
github.com/pion/srtp/v3 v3.0.4 h1:2Z6vDVxzrX3UHEgrUyIGM4rRouoC7v+NiF1IHtp9B5M=
|
||||||
github.com/pion/srtp/v3 v3.0.4/go.mod h1:1Jx3FwDoxpRaTh1oRV8A/6G1BnFL+QI82eK4ms8EEJQ=
|
github.com/pion/srtp/v3 v3.0.4/go.mod h1:1Jx3FwDoxpRaTh1oRV8A/6G1BnFL+QI82eK4ms8EEJQ=
|
||||||
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
|
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
|
||||||
@@ -252,37 +255,39 @@ github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQp
|
|||||||
github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E=
|
github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E=
|
||||||
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
|
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
|
||||||
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
|
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
|
||||||
github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM=
|
github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps=
|
||||||
github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA=
|
github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs=
|
||||||
github.com/pion/webrtc/v4 v4.0.14 h1:nyds/sFRR+HvmWoBa6wrL46sSfpArE0qR883MBW96lg=
|
github.com/pion/webrtc/v4 v4.1.1 h1:PMFPtLg1kpD2pVtun+LGUzA3k54JdFl87WO0Z1+HKug=
|
||||||
github.com/pion/webrtc/v4 v4.0.14/go.mod h1:R3+qTnQTS03UzwDarYecgioNf7DYgTsldxnCXB821Kk=
|
github.com/pion/webrtc/v4 v4.1.1/go.mod h1:cgEGkcpxGkT6Di2ClBYO5lP9mFXbCfEOrkYUpjjCQO4=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
|
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||||
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
|
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
|
github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4=
|
||||||
github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
|
github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.16.0 h1:xh6oHhKwnOJKMYiYBDWmkHqQPyiY40sny36Cmx2bbsM=
|
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||||
github.com/prometheus/procfs v0.16.0/go.mod h1:8veyXUu3nGP7oaCxhX6yeaM5u4stL2FeMXnCqhDthZg=
|
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||||
github.com/quic-go/quic-go v0.50.1 h1:unsgjFIUqW8a2oopkY7YNONpV1gYND6Nt9hnt1PN94Q=
|
github.com/quic-go/quic-go v0.52.0 h1:/SlHrCRElyaU6MaEPKqKr9z83sBg2v4FLLvWM+Z47pA=
|
||||||
github.com/quic-go/quic-go v0.50.1/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
|
github.com/quic-go/quic-go v0.52.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ=
|
||||||
github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 h1:4WFk6u3sOT6pLa1kQ50ZVdm8BQFgJNA117cepZxtLIg=
|
github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 h1:4WFk6u3sOT6pLa1kQ50ZVdm8BQFgJNA117cepZxtLIg=
|
||||||
github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66/go.mod h1:Vp72IJajgeOL6ddqrAhmp7IM9zbTcgkQxD/YdxrVwMw=
|
github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66/go.mod h1:Vp72IJajgeOL6ddqrAhmp7IM9zbTcgkQxD/YdxrVwMw=
|
||||||
github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk=
|
github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk=
|
||||||
github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU=
|
github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
@@ -318,9 +323,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
@@ -336,23 +339,20 @@ github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
|||||||
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||||
go.uber.org/dig v1.18.1 h1:rLww6NuajVjeQn+49u5NcezUJEGwd5uXmyoCKW2g5Es=
|
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||||
go.uber.org/dig v1.18.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
|
go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4=
|
||||||
go.uber.org/fx v1.23.0 h1:lIr/gYWQGfTwGcSXWXu4vP5Ws6iqnNEIY+F/aFzCKTg=
|
go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
|
||||||
go.uber.org/fx v1.23.0/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU=
|
go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg=
|
||||||
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
|
|
||||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||||
@@ -369,20 +369,18 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
|||||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
|
||||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
|
||||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||||
@@ -394,7 +392,6 @@ golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73r
|
|||||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
@@ -402,15 +399,15 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
@@ -426,8 +423,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -440,10 +437,10 @@ golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@@ -452,8 +449,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
@@ -464,33 +461,30 @@ golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
|||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||||
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -520,16 +514,14 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
|
|||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
lukechampine.com/blake3 v1.4.0 h1:xDbKOZCVbnZsfzM6mHSYcGRHZ3YrLDzqz8XnV4uaD5w=
|
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
||||||
lukechampine.com/blake3 v1.4.0/go.mod h1:MQJNQCTnR+kwOP/JEZSxj3MaQjp80FOFSNMMHXcSeX0=
|
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
|
||||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/libp2p/go-reuseport"
|
"github.com/libp2p/go-reuseport"
|
||||||
"github.com/pion/ice/v4"
|
"github.com/pion/ice/v4"
|
||||||
"github.com/pion/interceptor"
|
"github.com/pion/interceptor"
|
||||||
"github.com/pion/webrtc/v4"
|
"github.com/pion/webrtc/v4"
|
||||||
"log/slog"
|
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var globalWebRTCAPI *webrtc.API
|
var globalWebRTCAPI *webrtc.API
|
||||||
@@ -24,17 +25,9 @@ func InitWebRTCAPI() error {
|
|||||||
// Media engine
|
// Media engine
|
||||||
mediaEngine := &webrtc.MediaEngine{}
|
mediaEngine := &webrtc.MediaEngine{}
|
||||||
|
|
||||||
// Register additional header extensions to reduce latency
|
// Register our extensions
|
||||||
// Playout Delay
|
if err := RegisterExtensions(mediaEngine); err != nil {
|
||||||
if err := mediaEngine.RegisterHeaderExtension(webrtc.RTPHeaderExtensionCapability{
|
return fmt.Errorf("failed to register extensions: %w", err)
|
||||||
URI: ExtensionPlayoutDelay,
|
|
||||||
}, webrtc.RTPCodecTypeVideo); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := mediaEngine.RegisterHeaderExtension(webrtc.RTPHeaderExtensionCapability{
|
|
||||||
URI: ExtensionPlayoutDelay,
|
|
||||||
}, webrtc.RTPCodecTypeAudio); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default codecs cover most of our needs
|
// Default codecs cover most of our needs
|
||||||
@@ -75,9 +68,10 @@ func InitWebRTCAPI() error {
|
|||||||
// New in v4, reduces CPU usage and latency when enabled
|
// New in v4, reduces CPU usage and latency when enabled
|
||||||
settingEngine.EnableSCTPZeroChecksum(true)
|
settingEngine.EnableSCTPZeroChecksum(true)
|
||||||
|
|
||||||
nat11IPs := GetFlags().NAT11IPs
|
nat11IP := GetFlags().NAT11IP
|
||||||
if len(nat11IPs) > 0 {
|
if len(nat11IP) > 0 {
|
||||||
settingEngine.SetNAT1To1IPs(nat11IPs, webrtc.ICECandidateTypeHost)
|
settingEngine.SetNAT1To1IPs([]string{nat11IP}, webrtc.ICECandidateTypeSrflx)
|
||||||
|
slog.Info("Using NAT 1:1 IP for WebRTC", "nat11_ip", nat11IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
muxPort := GetFlags().UDPMuxPort
|
muxPort := GetFlags().UDPMuxPort
|
||||||
@@ -85,7 +79,7 @@ func InitWebRTCAPI() error {
|
|||||||
// Use reuseport to allow multiple listeners on the same port
|
// Use reuseport to allow multiple listeners on the same port
|
||||||
pktListener, err := reuseport.ListenPacket("udp", ":"+strconv.Itoa(muxPort))
|
pktListener, err := reuseport.ListenPacket("udp", ":"+strconv.Itoa(muxPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create UDP listener: %w", err)
|
return fmt.Errorf("failed to create WebRTC muxed UDP listener: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mux := ice.NewMultiUDPMuxDefault(ice.NewUDPMuxDefault(ice.UDPMuxParams{
|
mux := ice.NewMultiUDPMuxDefault(ice.NewUDPMuxDefault(ice.UDPMuxParams{
|
||||||
@@ -95,11 +89,14 @@ func InitWebRTCAPI() error {
|
|||||||
settingEngine.SetICEUDPMux(mux)
|
settingEngine.SetICEUDPMux(mux)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if flags.WebRTCUDPStart > 0 && flags.WebRTCUDPEnd > 0 && flags.WebRTCUDPStart < flags.WebRTCUDPEnd {
|
||||||
// Set the UDP port range used by WebRTC
|
// Set the UDP port range used by WebRTC
|
||||||
err = settingEngine.SetEphemeralUDPPortRange(uint16(flags.WebRTCUDPStart), uint16(flags.WebRTCUDPEnd))
|
err = settingEngine.SetEphemeralUDPPortRange(uint16(flags.WebRTCUDPStart), uint16(flags.WebRTCUDPEnd))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
slog.Info("Using WebRTC UDP Port Range", "start", flags.WebRTCUDPStart, "end", flags.WebRTCUDPEnd)
|
||||||
|
}
|
||||||
|
|
||||||
settingEngine.SetIncludeLoopbackCandidate(true) // Just in case
|
settingEngine.SetIncludeLoopbackCandidate(true) // Just in case
|
||||||
|
|
||||||
@@ -109,11 +106,6 @@ func InitWebRTCAPI() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetWebRTCAPI returns the global WebRTC API
|
|
||||||
func GetWebRTCAPI() *webrtc.API {
|
|
||||||
return globalWebRTCAPI
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreatePeerConnection sets up a new peer connection
|
// CreatePeerConnection sets up a new peer connection
|
||||||
func CreatePeerConnection(onClose func()) (*webrtc.PeerConnection, error) {
|
func CreatePeerConnection(onClose func()) (*webrtc.PeerConnection, error) {
|
||||||
pc, err := globalWebRTCAPI.NewPeerConnection(globalWebRTCConfig)
|
pc, err := globalWebRTCAPI.NewPeerConnection(globalWebRTCConfig)
|
||||||
|
|||||||
@@ -1,19 +1,51 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"errors"
|
||||||
"github.com/oklog/ulid/v2"
|
"fmt"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/oklog/ulid/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewULID() (ulid.ULID, error) {
|
func NewULID() (ulid.ULID, error) {
|
||||||
return ulid.New(ulid.Timestamp(time.Now()), ulid.Monotonic(rand.Reader, 0))
|
return ulid.New(ulid.Timestamp(time.Now()), ulid.Monotonic(rand.Reader, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to generate PSK from token
|
// GenerateED25519Key generates a new ED25519 key
|
||||||
func GeneratePSKFromToken(token string) ([]byte, error) {
|
func GenerateED25519Key() (ed25519.PrivateKey, error) {
|
||||||
// Simple hash-based PSK generation (32 bytes for libp2p)
|
_, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||||
hash := sha256.Sum256([]byte(token))
|
if err != nil {
|
||||||
return hash[:], nil
|
return nil, fmt.Errorf("failed to generate ED25519 key pair: %w", err)
|
||||||
|
}
|
||||||
|
return priv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveED25519Key saves an ED25519 private key to a path as a binary file
|
||||||
|
func SaveED25519Key(privateKey ed25519.PrivateKey, filePath string) error {
|
||||||
|
if privateKey == nil {
|
||||||
|
return errors.New("private key cannot be nil")
|
||||||
|
}
|
||||||
|
if len(privateKey) != ed25519.PrivateKeySize {
|
||||||
|
return errors.New("private key must be exactly 64 bytes for ED25519")
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filePath, privateKey, 0600); err != nil {
|
||||||
|
return fmt.Errorf("failed to save ED25519 key to %s: %w", filePath, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadED25519Key loads an ED25519 private key binary file from a path
|
||||||
|
func LoadED25519Key(filePath string) (ed25519.PrivateKey, error) {
|
||||||
|
data, err := os.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read ED25519 key from %s: %w", filePath, err)
|
||||||
|
}
|
||||||
|
if len(data) != ed25519.PrivateKeySize {
|
||||||
|
return nil, fmt.Errorf("ED25519 key must be exactly %d bytes, got %d", ed25519.PrivateKeySize, len(data))
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,45 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
|
import "github.com/pion/webrtc/v4"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ExtensionPlayoutDelay string = "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay"
|
ExtensionPlayoutDelay string = "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExtensionMap maps URIs to their IDs based on registration order
|
// ExtensionMap maps audio/video extension URIs to their IDs based on registration order
|
||||||
// IMPORTANT: This must match the order in which extensions are registered in common.go!
|
var ExtensionMap = map[webrtc.RTPCodecType]map[string]uint8{}
|
||||||
var ExtensionMap = map[string]uint8{
|
|
||||||
|
func RegisterExtensions(mediaEngine *webrtc.MediaEngine) error {
|
||||||
|
// Register additional header extensions to reduce latency
|
||||||
|
// Playout Delay (Video)
|
||||||
|
if err := mediaEngine.RegisterHeaderExtension(webrtc.RTPHeaderExtensionCapability{
|
||||||
|
URI: ExtensionPlayoutDelay,
|
||||||
|
}, webrtc.RTPCodecTypeVideo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Playout Delay (Audio)
|
||||||
|
if err := mediaEngine.RegisterHeaderExtension(webrtc.RTPHeaderExtensionCapability{
|
||||||
|
URI: ExtensionPlayoutDelay,
|
||||||
|
}, webrtc.RTPCodecTypeAudio); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the extension IDs for both audio and video
|
||||||
|
ExtensionMap[webrtc.RTPCodecTypeAudio] = map[string]uint8{
|
||||||
ExtensionPlayoutDelay: 1,
|
ExtensionPlayoutDelay: 1,
|
||||||
}
|
}
|
||||||
|
ExtensionMap[webrtc.RTPCodecTypeVideo] = map[string]uint8{
|
||||||
|
ExtensionPlayoutDelay: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetExtension(codecType webrtc.RTPCodecType, extURI string) (uint8, bool) {
|
||||||
|
cType, ok := ExtensionMap[codecType]
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
extID, ok := cType[extURI]
|
||||||
|
return extID, ok
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,47 +2,43 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"github.com/pion/webrtc/v4"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
"github.com/pion/webrtc/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
var globalFlags *Flags
|
var globalFlags *Flags
|
||||||
|
|
||||||
type Flags struct {
|
type Flags struct {
|
||||||
|
RegenIdentity bool // Remove old identity on startup and regenerate it
|
||||||
Verbose bool // Log everything to console
|
Verbose bool // Log everything to console
|
||||||
Debug bool // Enable debug mode, implies Verbose
|
Debug bool // Enable debug mode, implies Verbose
|
||||||
EndpointPort int // Port for HTTP/S and WS/S endpoint (TCP)
|
EndpointPort int // Port for HTTP/S and WS/S endpoint (TCP)
|
||||||
MeshPort int // Port for Mesh connections (TCP)
|
|
||||||
WebRTCUDPStart int // WebRTC UDP port range start - ignored if UDPMuxPort is set
|
WebRTCUDPStart int // WebRTC UDP port range start - ignored if UDPMuxPort is set
|
||||||
WebRTCUDPEnd int // WebRTC UDP port range end - ignored if UDPMuxPort is set
|
WebRTCUDPEnd int // WebRTC UDP port range end - ignored if UDPMuxPort is set
|
||||||
STUNServer string // WebRTC STUN server
|
STUNServer string // WebRTC STUN server
|
||||||
UDPMuxPort int // WebRTC UDP mux port - if set, overrides UDP port range
|
UDPMuxPort int // WebRTC UDP mux port - if set, overrides UDP port range
|
||||||
AutoAddLocalIP bool // Automatically add local IP to NAT 1 to 1 IPs
|
AutoAddLocalIP bool // Automatically add local IP to NAT 1 to 1 IPs
|
||||||
NAT11IPs []string // WebRTC NAT 1 to 1 IP(s) - allows specifying host IP(s) if behind NAT
|
NAT11IP string // WebRTC NAT 1 to 1 IP - allows specifying IP of relay if behind NAT
|
||||||
TLSCert string // Path to TLS certificate
|
PersistDir string // Directory to save persistent data to
|
||||||
TLSKey string // Path to TLS key
|
|
||||||
ControlSecret string // Shared secret for this relay's control endpoint
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (flags *Flags) DebugLog() {
|
func (flags *Flags) DebugLog() {
|
||||||
slog.Info("Relay flags",
|
slog.Debug("Relay flags",
|
||||||
|
"regenIdentity", flags.RegenIdentity,
|
||||||
"verbose", flags.Verbose,
|
"verbose", flags.Verbose,
|
||||||
"debug", flags.Debug,
|
"debug", flags.Debug,
|
||||||
"endpointPort", flags.EndpointPort,
|
"endpointPort", flags.EndpointPort,
|
||||||
"meshPort", flags.MeshPort,
|
|
||||||
"webrtcUDPStart", flags.WebRTCUDPStart,
|
"webrtcUDPStart", flags.WebRTCUDPStart,
|
||||||
"webrtcUDPEnd", flags.WebRTCUDPEnd,
|
"webrtcUDPEnd", flags.WebRTCUDPEnd,
|
||||||
"stunServer", flags.STUNServer,
|
"stunServer", flags.STUNServer,
|
||||||
"webrtcUDPMux", flags.UDPMuxPort,
|
"webrtcUDPMux", flags.UDPMuxPort,
|
||||||
"autoAddLocalIP", flags.AutoAddLocalIP,
|
"autoAddLocalIP", flags.AutoAddLocalIP,
|
||||||
"webrtcNAT11IPs", strings.Join(flags.NAT11IPs, ","),
|
"webrtcNAT11IPs", flags.NAT11IP,
|
||||||
"tlsCert", flags.TLSCert,
|
"persistDir", flags.PersistDir,
|
||||||
"tlsKey", flags.TLSKey,
|
|
||||||
"controlSecret", flags.ControlSecret,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,29 +72,25 @@ func InitFlags() {
|
|||||||
// Create Flags struct
|
// Create Flags struct
|
||||||
globalFlags = &Flags{}
|
globalFlags = &Flags{}
|
||||||
// Get flags
|
// Get flags
|
||||||
|
flag.BoolVar(&globalFlags.RegenIdentity, "regenIdentity", getEnvAsBool("REGEN_IDENTITY", false), "Regenerate identity on startup")
|
||||||
flag.BoolVar(&globalFlags.Verbose, "verbose", getEnvAsBool("VERBOSE", false), "Verbose mode")
|
flag.BoolVar(&globalFlags.Verbose, "verbose", getEnvAsBool("VERBOSE", false), "Verbose mode")
|
||||||
flag.BoolVar(&globalFlags.Debug, "debug", getEnvAsBool("DEBUG", false), "Debug mode")
|
flag.BoolVar(&globalFlags.Debug, "debug", getEnvAsBool("DEBUG", false), "Debug mode")
|
||||||
flag.IntVar(&globalFlags.EndpointPort, "endpointPort", getEnvAsInt("ENDPOINT_PORT", 8088), "HTTP endpoint port")
|
flag.IntVar(&globalFlags.EndpointPort, "endpointPort", getEnvAsInt("ENDPOINT_PORT", 8088), "HTTP endpoint port")
|
||||||
flag.IntVar(&globalFlags.MeshPort, "meshPort", getEnvAsInt("MESH_PORT", 8089), "Mesh connections TCP port")
|
flag.IntVar(&globalFlags.WebRTCUDPStart, "webrtcUDPStart", getEnvAsInt("WEBRTC_UDP_START", 0), "WebRTC UDP port range start")
|
||||||
flag.IntVar(&globalFlags.WebRTCUDPStart, "webrtcUDPStart", getEnvAsInt("WEBRTC_UDP_START", 10000), "WebRTC UDP port range start")
|
flag.IntVar(&globalFlags.WebRTCUDPEnd, "webrtcUDPEnd", getEnvAsInt("WEBRTC_UDP_END", 0), "WebRTC UDP port range end")
|
||||||
flag.IntVar(&globalFlags.WebRTCUDPEnd, "webrtcUDPEnd", getEnvAsInt("WEBRTC_UDP_END", 20000), "WebRTC UDP port range end")
|
|
||||||
flag.StringVar(&globalFlags.STUNServer, "stunServer", getEnvAsString("STUN_SERVER", "stun.l.google.com:19302"), "WebRTC STUN server")
|
flag.StringVar(&globalFlags.STUNServer, "stunServer", getEnvAsString("STUN_SERVER", "stun.l.google.com:19302"), "WebRTC STUN server")
|
||||||
flag.IntVar(&globalFlags.UDPMuxPort, "webrtcUDPMux", getEnvAsInt("WEBRTC_UDP_MUX", 8088), "WebRTC UDP mux port")
|
flag.IntVar(&globalFlags.UDPMuxPort, "webrtcUDPMux", getEnvAsInt("WEBRTC_UDP_MUX", 8088), "WebRTC UDP mux port")
|
||||||
flag.BoolVar(&globalFlags.AutoAddLocalIP, "autoAddLocalIP", getEnvAsBool("AUTO_ADD_LOCAL_IP", true), "Automatically add local IP to NAT 1 to 1 IPs")
|
flag.BoolVar(&globalFlags.AutoAddLocalIP, "autoAddLocalIP", getEnvAsBool("AUTO_ADD_LOCAL_IP", true), "Automatically add local IP to NAT 1 to 1 IPs")
|
||||||
// String with comma separated IPs
|
// String with comma separated IPs
|
||||||
nat11IPs := ""
|
nat11IP := ""
|
||||||
flag.StringVar(&nat11IPs, "webrtcNAT11IPs", getEnvAsString("WEBRTC_NAT_IPS", ""), "WebRTC NAT 1 to 1 IP(s), comma delimited")
|
flag.StringVar(&nat11IP, "webrtcNAT11IP", getEnvAsString("WEBRTC_NAT_IP", ""), "WebRTC NAT 1 to 1 IP")
|
||||||
flag.StringVar(&globalFlags.TLSCert, "tlsCert", getEnvAsString("TLS_CERT", ""), "Path to TLS certificate")
|
flag.StringVar(&globalFlags.PersistDir, "persistDir", getEnvAsString("PERSIST_DIR", "./persist-data"), "Directory to save persistent data to")
|
||||||
flag.StringVar(&globalFlags.TLSKey, "tlsKey", getEnvAsString("TLS_KEY", ""), "Path to TLS key")
|
|
||||||
flag.StringVar(&globalFlags.ControlSecret, "controlSecret", getEnvAsString("CONTROL_SECRET", ""), "Shared secret for control endpoint")
|
|
||||||
// Parse flags
|
// Parse flags
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
// If debug is enabled, verbose is also enabled
|
// If debug is enabled, verbose is also enabled
|
||||||
if globalFlags.Debug {
|
if globalFlags.Debug {
|
||||||
globalFlags.Verbose = true
|
globalFlags.Verbose = true
|
||||||
// If Debug is enabled, set ControlSecret to 1234
|
|
||||||
globalFlags.ControlSecret = "1234"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ICE STUN servers
|
// ICE STUN servers
|
||||||
@@ -108,24 +100,11 @@ func InitFlags() {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize NAT 1 to 1 IPs
|
|
||||||
globalFlags.NAT11IPs = []string{}
|
|
||||||
|
|
||||||
// Get local IP
|
|
||||||
if globalFlags.AutoAddLocalIP {
|
|
||||||
globalFlags.NAT11IPs = append(globalFlags.NAT11IPs, getLocalIP())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse NAT 1 to 1 IPs from string
|
// Parse NAT 1 to 1 IPs from string
|
||||||
if len(nat11IPs) > 0 {
|
if len(nat11IP) > 0 {
|
||||||
split := strings.Split(nat11IPs, ",")
|
globalFlags.NAT11IP = nat11IP
|
||||||
if len(split) > 0 {
|
} else if globalFlags.AutoAddLocalIP {
|
||||||
for _, ip := range split {
|
globalFlags.NAT11IP = getLocalIP()
|
||||||
globalFlags.NAT11IPs = append(globalFlags.NAT11IPs, ip)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
globalFlags.NAT11IPs = append(globalFlags.NAT11IPs, nat11IPs)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
|
||||||
gen "relay/internal/proto"
|
gen "relay/internal/proto"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TimestampEntry struct {
|
type TimestampEntry struct {
|
||||||
|
|||||||
175
packages/relay/internal/common/safebufio.go
Normal file
175
packages/relay/internal/common/safebufio.go
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MaxSize is the maximum allowed data size (1MB)
|
||||||
|
const MaxSize = 1024 * 1024
|
||||||
|
|
||||||
|
// SafeBufioRW wraps a bufio.ReadWriter for sending and receiving JSON and protobufs safely
|
||||||
|
type SafeBufioRW struct {
|
||||||
|
brw *bufio.ReadWriter
|
||||||
|
mutex sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSafeBufioRW(brw *bufio.ReadWriter) *SafeBufioRW {
|
||||||
|
return &SafeBufioRW{brw: brw}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendJSON serializes the given data as JSON and sends it with a 4-byte length prefix
|
||||||
|
func (bu *SafeBufioRW) SendJSON(data interface{}) error {
|
||||||
|
bu.mutex.Lock()
|
||||||
|
defer bu.mutex.Unlock()
|
||||||
|
|
||||||
|
jsonData, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(jsonData) > MaxSize {
|
||||||
|
return errors.New("JSON data exceeds maximum size")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the 4-byte length prefix
|
||||||
|
if err = binary.Write(bu.brw, binary.BigEndian, uint32(len(jsonData))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the JSON data
|
||||||
|
if _, err = bu.brw.Write(jsonData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush the writer to ensure data is sent
|
||||||
|
return bu.brw.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReceiveJSON reads a 4-byte length prefix, then reads and unmarshals the JSON
|
||||||
|
func (bu *SafeBufioRW) ReceiveJSON(dest interface{}) error {
|
||||||
|
bu.mutex.RLock()
|
||||||
|
defer bu.mutex.RUnlock()
|
||||||
|
|
||||||
|
// Read the 4-byte length prefix
|
||||||
|
var length uint32
|
||||||
|
if err := binary.Read(bu.brw, binary.BigEndian, &length); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if length > MaxSize {
|
||||||
|
return errors.New("received JSON data exceeds maximum size")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the JSON data
|
||||||
|
data := make([]byte, length)
|
||||||
|
if _, err := io.ReadFull(bu.brw, data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Unmarshal(data, dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive reads a 4-byte length prefix, then reads the raw data
|
||||||
|
func (bu *SafeBufioRW) Receive() ([]byte, error) {
|
||||||
|
bu.mutex.RLock()
|
||||||
|
defer bu.mutex.RUnlock()
|
||||||
|
|
||||||
|
// Read the 4-byte length prefix
|
||||||
|
var length uint32
|
||||||
|
if err := binary.Read(bu.brw, binary.BigEndian, &length); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if length > MaxSize {
|
||||||
|
return nil, errors.New("received data exceeds maximum size")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the raw data
|
||||||
|
data := make([]byte, length)
|
||||||
|
if _, err := io.ReadFull(bu.brw, data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendProto serializes the given protobuf message and sends it with a 4-byte length prefix
|
||||||
|
func (bu *SafeBufioRW) SendProto(msg proto.Message) error {
|
||||||
|
bu.mutex.Lock()
|
||||||
|
defer bu.mutex.Unlock()
|
||||||
|
|
||||||
|
protoData, err := proto.Marshal(msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(protoData) > MaxSize {
|
||||||
|
return errors.New("protobuf data exceeds maximum size")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the 4-byte length prefix
|
||||||
|
if err = binary.Write(bu.brw, binary.BigEndian, uint32(len(protoData))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the Protobuf data
|
||||||
|
if _, err := bu.brw.Write(protoData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush the writer to ensure data is sent
|
||||||
|
return bu.brw.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReceiveProto reads a 4-byte length prefix, then reads and unmarshals the protobuf
|
||||||
|
func (bu *SafeBufioRW) ReceiveProto(msg proto.Message) error {
|
||||||
|
bu.mutex.RLock()
|
||||||
|
defer bu.mutex.RUnlock()
|
||||||
|
|
||||||
|
// Read the 4-byte length prefix
|
||||||
|
var length uint32
|
||||||
|
if err := binary.Read(bu.brw, binary.BigEndian, &length); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if length > MaxSize {
|
||||||
|
return errors.New("received Protobuf data exceeds maximum size")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the Protobuf data
|
||||||
|
data := make([]byte, length)
|
||||||
|
if _, err := io.ReadFull(bu.brw, data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return proto.Unmarshal(data, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes raw data to the underlying buffer
|
||||||
|
func (bu *SafeBufioRW) Write(data []byte) (int, error) {
|
||||||
|
bu.mutex.Lock()
|
||||||
|
defer bu.mutex.Unlock()
|
||||||
|
|
||||||
|
if len(data) > MaxSize {
|
||||||
|
return 0, errors.New("data exceeds maximum size")
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := bu.brw.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush the writer to ensure data is sent
|
||||||
|
if err = bu.brw.Flush(); err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
@@ -1,18 +1,11 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"encoding/json"
|
||||||
"reflect"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
ErrKeyNotFound = errors.New("key not found")
|
|
||||||
ErrValueNotPointer = errors.New("value is not a pointer")
|
|
||||||
ErrFieldNotFound = errors.New("field not found")
|
|
||||||
ErrTypeMismatch = errors.New("type mismatch")
|
|
||||||
)
|
|
||||||
|
|
||||||
// SafeMap is a generic thread-safe map with its own mutex
|
// SafeMap is a generic thread-safe map with its own mutex
|
||||||
type SafeMap[K comparable, V any] struct {
|
type SafeMap[K comparable, V any] struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
@@ -34,6 +27,14 @@ func (sm *SafeMap[K, V]) Get(key K) (V, bool) {
|
|||||||
return v, ok
|
return v, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Has checks if a key exists in the map
|
||||||
|
func (sm *SafeMap[K, V]) Has(key K) bool {
|
||||||
|
sm.mu.RLock()
|
||||||
|
defer sm.mu.RUnlock()
|
||||||
|
_, ok := sm.m[key]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
// Set adds or updates a value in the map
|
// Set adds or updates a value in the map
|
||||||
func (sm *SafeMap[K, V]) Set(key K, value V) {
|
func (sm *SafeMap[K, V]) Set(key K, value V) {
|
||||||
sm.mu.Lock()
|
sm.mu.Lock()
|
||||||
@@ -66,36 +67,31 @@ func (sm *SafeMap[K, V]) Copy() map[K]V {
|
|||||||
return copied
|
return copied
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates a specific field in the value data
|
// Range iterates over the map and applies a function to each key-value pair
|
||||||
func (sm *SafeMap[K, V]) Update(key K, fieldName string, newValue any) error {
|
func (sm *SafeMap[K, V]) Range(f func(K, V) bool) {
|
||||||
|
sm.mu.RLock()
|
||||||
|
defer sm.mu.RUnlock()
|
||||||
|
for k, v := range sm.m {
|
||||||
|
if !f(k, v) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *SafeMap[K, V]) MarshalJSON() ([]byte, error) {
|
||||||
|
sm.mu.RLock()
|
||||||
|
defer sm.mu.RUnlock()
|
||||||
|
return json.Marshal(sm.m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *SafeMap[K, V]) UnmarshalJSON(data []byte) error {
|
||||||
sm.mu.Lock()
|
sm.mu.Lock()
|
||||||
defer sm.mu.Unlock()
|
defer sm.mu.Unlock()
|
||||||
|
return json.Unmarshal(data, &sm.m)
|
||||||
v, ok := sm.m[key]
|
|
||||||
if !ok {
|
|
||||||
return ErrKeyNotFound
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use reflect to update the field
|
func (sm *SafeMap[K, V]) String() string {
|
||||||
rv := reflect.ValueOf(v)
|
sm.mu.RLock()
|
||||||
if rv.Kind() != reflect.Ptr {
|
defer sm.mu.RUnlock()
|
||||||
return ErrValueNotPointer
|
return fmt.Sprintf("%+v", sm.m)
|
||||||
}
|
|
||||||
|
|
||||||
rv = rv.Elem()
|
|
||||||
// Check if the field exists
|
|
||||||
field := rv.FieldByName(fieldName)
|
|
||||||
if !field.IsValid() || !field.CanSet() {
|
|
||||||
return ErrFieldNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
newRV := reflect.ValueOf(newValue)
|
|
||||||
if newRV.Type() != field.Type() {
|
|
||||||
return ErrTypeMismatch
|
|
||||||
}
|
|
||||||
|
|
||||||
field.Set(newRV)
|
|
||||||
sm.m[key] = v
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
package connections
|
package connections
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pion/webrtc/v4"
|
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
gen "relay/internal/proto"
|
gen "relay/internal/proto"
|
||||||
|
|
||||||
|
"github.com/pion/webrtc/v4"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type OnMessageCallback func(data []byte)
|
||||||
|
|
||||||
// NestriDataChannel is a custom data channel with callbacks
|
// NestriDataChannel is a custom data channel with callbacks
|
||||||
type NestriDataChannel struct {
|
type NestriDataChannel struct {
|
||||||
*webrtc.DataChannel
|
*webrtc.DataChannel
|
||||||
@@ -37,7 +40,7 @@ func NewNestriDataChannel(dc *webrtc.DataChannel) *NestriDataChannel {
|
|||||||
// Handle message type callback
|
// Handle message type callback
|
||||||
if callback, ok := ndc.callbacks["input"]; ok {
|
if callback, ok := ndc.callbacks["input"]; ok {
|
||||||
go callback(msg.Data)
|
go callback(msg.Data)
|
||||||
} // TODO: Log unknown message type?
|
} // We don't care about unhandled messages
|
||||||
})
|
})
|
||||||
|
|
||||||
return ndc
|
return ndc
|
||||||
|
|||||||
@@ -1,18 +1,32 @@
|
|||||||
package connections
|
package connections
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pion/webrtc/v4"
|
"encoding/json"
|
||||||
"relay/internal/common"
|
"relay/internal/common"
|
||||||
"time"
|
|
||||||
|
"github.com/pion/webrtc/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MessageBase is the base type for WS/DC messages.
|
// MessageBase is the base type for any JSON message
|
||||||
type MessageBase struct {
|
type MessageBase struct {
|
||||||
PayloadType string `json:"payload_type"`
|
Type string `json:"payload_type"`
|
||||||
Latency *common.LatencyTracker `json:"latency,omitempty"`
|
Latency *common.LatencyTracker `json:"latency,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MessageLog represents a log message.
|
type MessageRaw struct {
|
||||||
|
MessageBase
|
||||||
|
Data json.RawMessage `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMessageRaw(t string, data json.RawMessage) *MessageRaw {
|
||||||
|
return &MessageRaw{
|
||||||
|
MessageBase: MessageBase{
|
||||||
|
Type: t,
|
||||||
|
},
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type MessageLog struct {
|
type MessageLog struct {
|
||||||
MessageBase
|
MessageBase
|
||||||
Level string `json:"level"`
|
Level string `json:"level"`
|
||||||
@@ -20,7 +34,17 @@ type MessageLog struct {
|
|||||||
Time string `json:"time"`
|
Time string `json:"time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MessageMetrics represents a metrics/heartbeat message.
|
func NewMessageLog(t string, level, message, time string) *MessageLog {
|
||||||
|
return &MessageLog{
|
||||||
|
MessageBase: MessageBase{
|
||||||
|
Type: t,
|
||||||
|
},
|
||||||
|
Level: level,
|
||||||
|
Message: message,
|
||||||
|
Time: time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type MessageMetrics struct {
|
type MessageMetrics struct {
|
||||||
MessageBase
|
MessageBase
|
||||||
UsageCPU float64 `json:"usage_cpu"`
|
UsageCPU float64 `json:"usage_cpu"`
|
||||||
@@ -29,104 +53,42 @@ type MessageMetrics struct {
|
|||||||
PipelineLatency float64 `json:"pipeline_latency"`
|
PipelineLatency float64 `json:"pipeline_latency"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MessageICECandidate represents an ICE candidate message.
|
func NewMessageMetrics(t string, usageCPU, usageMemory float64, uptime uint64, pipelineLatency float64) *MessageMetrics {
|
||||||
type MessageICECandidate struct {
|
return &MessageMetrics{
|
||||||
MessageBase
|
MessageBase: MessageBase{
|
||||||
Candidate webrtc.ICECandidateInit `json:"candidate"`
|
Type: t,
|
||||||
}
|
},
|
||||||
|
|
||||||
// MessageSDP represents an SDP message.
|
|
||||||
type MessageSDP struct {
|
|
||||||
MessageBase
|
|
||||||
SDP webrtc.SessionDescription `json:"sdp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// JoinerType is an enum for the type of incoming room joiner
|
|
||||||
type JoinerType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
JoinerNode JoinerType = iota
|
|
||||||
JoinerClient
|
|
||||||
)
|
|
||||||
|
|
||||||
func (jt *JoinerType) String() string {
|
|
||||||
switch *jt {
|
|
||||||
case JoinerNode:
|
|
||||||
return "node"
|
|
||||||
case JoinerClient:
|
|
||||||
return "client"
|
|
||||||
default:
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MessageJoin is used to tell us that either participant or ingest wants to join the room
|
|
||||||
type MessageJoin struct {
|
|
||||||
MessageBase
|
|
||||||
JoinerType JoinerType `json:"joiner_type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AnswerType is an enum for the type of answer, signaling Room state for a joiner
|
|
||||||
type AnswerType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
AnswerOffline AnswerType = iota // For participant/client, when the room is offline without stream
|
|
||||||
AnswerInUse // For ingest/node joiner, when the room is already in use by another ingest/node
|
|
||||||
AnswerOK // For both, when the join request is handled successfully
|
|
||||||
)
|
|
||||||
|
|
||||||
// MessageAnswer is used to send the answer to a join request
|
|
||||||
type MessageAnswer struct {
|
|
||||||
MessageBase
|
|
||||||
AnswerType AnswerType `json:"answer_type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendLogMessageWS sends a log message to the given WebSocket connection.
|
|
||||||
func (ws *SafeWebSocket) SendLogMessageWS(level, message string) error {
|
|
||||||
msg := MessageLog{
|
|
||||||
MessageBase: MessageBase{PayloadType: "log"},
|
|
||||||
Level: level,
|
|
||||||
Message: message,
|
|
||||||
Time: time.Now().Format(time.RFC3339),
|
|
||||||
}
|
|
||||||
return ws.SendJSON(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendMetricsMessageWS sends a metrics message to the given WebSocket connection.
|
|
||||||
func (ws *SafeWebSocket) SendMetricsMessageWS(usageCPU, usageMemory float64, uptime uint64, pipelineLatency float64) error {
|
|
||||||
msg := MessageMetrics{
|
|
||||||
MessageBase: MessageBase{PayloadType: "metrics"},
|
|
||||||
UsageCPU: usageCPU,
|
UsageCPU: usageCPU,
|
||||||
UsageMemory: usageMemory,
|
UsageMemory: usageMemory,
|
||||||
Uptime: uptime,
|
Uptime: uptime,
|
||||||
PipelineLatency: pipelineLatency,
|
PipelineLatency: pipelineLatency,
|
||||||
}
|
}
|
||||||
return ws.SendJSON(msg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendICECandidateMessageWS sends an ICE candidate message to the given WebSocket connection.
|
type MessageICE struct {
|
||||||
func (ws *SafeWebSocket) SendICECandidateMessageWS(candidate webrtc.ICECandidateInit) error {
|
MessageBase
|
||||||
msg := MessageICECandidate{
|
Candidate webrtc.ICECandidateInit `json:"candidate"`
|
||||||
MessageBase: MessageBase{PayloadType: "ice"},
|
}
|
||||||
|
|
||||||
|
func NewMessageICE(t string, candidate webrtc.ICECandidateInit) *MessageICE {
|
||||||
|
return &MessageICE{
|
||||||
|
MessageBase: MessageBase{
|
||||||
|
Type: t,
|
||||||
|
},
|
||||||
Candidate: candidate,
|
Candidate: candidate,
|
||||||
}
|
}
|
||||||
return ws.SendJSON(msg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendSDPMessageWS sends an SDP message to the given WebSocket connection.
|
type MessageSDP struct {
|
||||||
func (ws *SafeWebSocket) SendSDPMessageWS(sdp webrtc.SessionDescription) error {
|
MessageBase
|
||||||
msg := MessageSDP{
|
SDP webrtc.SessionDescription `json:"sdp"`
|
||||||
MessageBase: MessageBase{PayloadType: "sdp"},
|
}
|
||||||
|
|
||||||
|
func NewMessageSDP(t string, sdp webrtc.SessionDescription) *MessageSDP {
|
||||||
|
return &MessageSDP{
|
||||||
|
MessageBase: MessageBase{
|
||||||
|
Type: t,
|
||||||
|
},
|
||||||
SDP: sdp,
|
SDP: sdp,
|
||||||
}
|
}
|
||||||
return ws.SendJSON(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendAnswerMessageWS sends an answer message to the given WebSocket connection.
|
|
||||||
func (ws *SafeWebSocket) SendAnswerMessageWS(answer AnswerType) error {
|
|
||||||
msg := MessageAnswer{
|
|
||||||
MessageBase: MessageBase{PayloadType: "answer"},
|
|
||||||
AnswerType: answer,
|
|
||||||
}
|
|
||||||
return ws.SendJSON(msg)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
package connections
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/pion/webrtc/v4"
|
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
gen "relay/internal/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SendMeshHandshake sends a handshake message to another relay.
|
|
||||||
func (ws *SafeWebSocket) SendMeshHandshake(relayID, publicKey string) error {
|
|
||||||
msg := &gen.MeshMessage{
|
|
||||||
Type: &gen.MeshMessage_Handshake{
|
|
||||||
Handshake: &gen.Handshake{
|
|
||||||
RelayId: relayID,
|
|
||||||
DhPublicKey: publicKey,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
data, err := proto.Marshal(msg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return ws.SendBinary(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendMeshHandshakeResponse sends a handshake response to a relay.
|
|
||||||
func (ws *SafeWebSocket) SendMeshHandshakeResponse(relayID, dhPublicKey string, approvals map[string]string) error {
|
|
||||||
msg := &gen.MeshMessage{
|
|
||||||
Type: &gen.MeshMessage_HandshakeResponse{
|
|
||||||
HandshakeResponse: &gen.HandshakeResponse{
|
|
||||||
RelayId: relayID,
|
|
||||||
DhPublicKey: dhPublicKey,
|
|
||||||
Approvals: approvals,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
data, err := proto.Marshal(msg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return ws.SendBinary(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendMeshForwardSDP sends a forwarded SDP message to another relay
|
|
||||||
func (ws *SafeWebSocket) SendMeshForwardSDP(roomName, participantID string, sdp webrtc.SessionDescription) error {
|
|
||||||
msg := &gen.MeshMessage{
|
|
||||||
Type: &gen.MeshMessage_ForwardSdp{
|
|
||||||
ForwardSdp: &gen.ForwardSDP{
|
|
||||||
RoomName: roomName,
|
|
||||||
ParticipantId: participantID,
|
|
||||||
Sdp: sdp.SDP,
|
|
||||||
Type: sdp.Type.String(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
data, err := proto.Marshal(msg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return ws.SendBinary(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendMeshForwardICE sends a forwarded ICE candidate to another relay
|
|
||||||
func (ws *SafeWebSocket) SendMeshForwardICE(roomName, participantID string, candidate webrtc.ICECandidateInit) error {
|
|
||||||
var sdpMLineIndex uint32
|
|
||||||
if candidate.SDPMLineIndex != nil {
|
|
||||||
sdpMLineIndex = uint32(*candidate.SDPMLineIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := &gen.MeshMessage{
|
|
||||||
Type: &gen.MeshMessage_ForwardIce{
|
|
||||||
ForwardIce: &gen.ForwardICE{
|
|
||||||
RoomName: roomName,
|
|
||||||
ParticipantId: participantID,
|
|
||||||
Candidate: &gen.ICECandidateInit{
|
|
||||||
Candidate: candidate.Candidate,
|
|
||||||
SdpMid: candidate.SDPMid,
|
|
||||||
SdpMLineIndex: &sdpMLineIndex,
|
|
||||||
UsernameFragment: candidate.UsernameFragment,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
data, err := proto.Marshal(msg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return ws.SendBinary(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ws *SafeWebSocket) SendMeshForwardIngest(roomName string) error {
|
|
||||||
msg := &gen.MeshMessage{
|
|
||||||
Type: &gen.MeshMessage_ForwardIngest{
|
|
||||||
ForwardIngest: &gen.ForwardIngest{
|
|
||||||
RoomName: roomName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
data, err := proto.Marshal(msg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return ws.SendBinary(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ws *SafeWebSocket) SendMeshStreamRequest(roomName string) error {
|
|
||||||
msg := &gen.MeshMessage{
|
|
||||||
Type: &gen.MeshMessage_StreamRequest{
|
|
||||||
StreamRequest: &gen.StreamRequest{
|
|
||||||
RoomName: roomName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
data, err := proto.Marshal(msg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return ws.SendBinary(data)
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
package connections
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
"log/slog"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OnMessageCallback is a callback for messages of given type
|
|
||||||
type OnMessageCallback func(data []byte)
|
|
||||||
|
|
||||||
// SafeWebSocket is a websocket with a mutex
|
|
||||||
type SafeWebSocket struct {
|
|
||||||
*websocket.Conn
|
|
||||||
sync.Mutex
|
|
||||||
closed bool
|
|
||||||
closeCallback func() // Callback to call on close
|
|
||||||
closeChan chan struct{} // Channel to signal closure
|
|
||||||
callbacks map[string]OnMessageCallback // MessageBase type -> callback
|
|
||||||
binaryCallback OnMessageCallback // Binary message callback
|
|
||||||
sharedSecret []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSafeWebSocket creates a new SafeWebSocket from *websocket.Conn
|
|
||||||
func NewSafeWebSocket(conn *websocket.Conn) *SafeWebSocket {
|
|
||||||
ws := &SafeWebSocket{
|
|
||||||
Conn: conn,
|
|
||||||
closed: false,
|
|
||||||
closeCallback: nil,
|
|
||||||
closeChan: make(chan struct{}),
|
|
||||||
callbacks: make(map[string]OnMessageCallback),
|
|
||||||
binaryCallback: nil,
|
|
||||||
sharedSecret: nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Launch a goroutine to handle messages
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
// Read message
|
|
||||||
kind, data, err := ws.Conn.ReadMessage()
|
|
||||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure, websocket.CloseNoStatusReceived) {
|
|
||||||
// If unexpected close error, break
|
|
||||||
slog.Debug("WebSocket closed unexpectedly", "err", err)
|
|
||||||
break
|
|
||||||
} else if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway, websocket.CloseAbnormalClosure, websocket.CloseNoStatusReceived) {
|
|
||||||
break
|
|
||||||
} else if err != nil {
|
|
||||||
slog.Error("Failed reading WebSocket message", "err", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
switch kind {
|
|
||||||
case websocket.TextMessage:
|
|
||||||
// Decode message
|
|
||||||
var msg MessageBase
|
|
||||||
if err = json.Unmarshal(data, &msg); err != nil {
|
|
||||||
slog.Error("Failed decoding WebSocket message", "err", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle message type callback
|
|
||||||
if callback, ok := ws.callbacks[msg.PayloadType]; ok {
|
|
||||||
callback(data)
|
|
||||||
} // TODO: Log unknown message payload type?
|
|
||||||
break
|
|
||||||
case websocket.BinaryMessage:
|
|
||||||
// Handle binary message callback
|
|
||||||
if ws.binaryCallback != nil {
|
|
||||||
ws.binaryCallback(data)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
slog.Warn("Unknown WebSocket message type", "type", kind)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signal closure to callback first
|
|
||||||
if ws.closeCallback != nil {
|
|
||||||
ws.closeCallback()
|
|
||||||
}
|
|
||||||
close(ws.closeChan)
|
|
||||||
ws.closed = true
|
|
||||||
}()
|
|
||||||
|
|
||||||
return ws
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSharedSecret sets the shared secret for the websocket
|
|
||||||
func (ws *SafeWebSocket) SetSharedSecret(secret []byte) {
|
|
||||||
ws.sharedSecret = secret
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSharedSecret returns the shared secret for the websocket
|
|
||||||
func (ws *SafeWebSocket) GetSharedSecret() []byte {
|
|
||||||
return ws.sharedSecret
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendJSON writes JSON to a websocket with a mutex
|
|
||||||
func (ws *SafeWebSocket) SendJSON(v interface{}) error {
|
|
||||||
ws.Lock()
|
|
||||||
defer ws.Unlock()
|
|
||||||
return ws.Conn.WriteJSON(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendBinary writes binary to a websocket with a mutex
|
|
||||||
func (ws *SafeWebSocket) SendBinary(data []byte) error {
|
|
||||||
ws.Lock()
|
|
||||||
defer ws.Unlock()
|
|
||||||
return ws.Conn.WriteMessage(websocket.BinaryMessage, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterMessageCallback sets the callback for binary message of given type
|
|
||||||
func (ws *SafeWebSocket) RegisterMessageCallback(msgType string, callback OnMessageCallback) {
|
|
||||||
if ws.callbacks == nil {
|
|
||||||
ws.callbacks = make(map[string]OnMessageCallback)
|
|
||||||
}
|
|
||||||
ws.callbacks[msgType] = callback
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterBinaryMessageCallback sets the callback for all binary messages
|
|
||||||
func (ws *SafeWebSocket) RegisterBinaryMessageCallback(callback OnMessageCallback) {
|
|
||||||
ws.binaryCallback = callback
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnregisterMessageCallback removes the callback for binary message of given type
|
|
||||||
func (ws *SafeWebSocket) UnregisterMessageCallback(msgType string) {
|
|
||||||
if ws.callbacks != nil {
|
|
||||||
delete(ws.callbacks, msgType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnregisterBinaryMessageCallback removes the callback for all binary messages
|
|
||||||
func (ws *SafeWebSocket) UnregisterBinaryMessageCallback() {
|
|
||||||
ws.binaryCallback = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterOnClose sets the callback for websocket closing
|
|
||||||
func (ws *SafeWebSocket) RegisterOnClose(callback func()) {
|
|
||||||
ws.closeCallback = func() {
|
|
||||||
// Clear our callbacks
|
|
||||||
ws.callbacks = nil
|
|
||||||
ws.binaryCallback = nil
|
|
||||||
// Call the callback
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Closed returns a channel that closes when the WebSocket connection is terminated
|
|
||||||
func (ws *SafeWebSocket) Closed() <-chan struct{} {
|
|
||||||
return ws.closeChan
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsClosed returns true if the WebSocket connection is closed
|
|
||||||
func (ws *SafeWebSocket) IsClosed() bool {
|
|
||||||
return ws.closed
|
|
||||||
}
|
|
||||||
13
packages/relay/internal/core/consts.go
Normal file
13
packages/relay/internal/core/consts.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// --- Constants ---
|
||||||
|
const (
|
||||||
|
// PubSub Topics
|
||||||
|
roomStateTopicName = "room-states"
|
||||||
|
relayMetricsTopicName = "relay-metrics"
|
||||||
|
|
||||||
|
// Timers and Intervals
|
||||||
|
metricsPublishInterval = 15 * time.Second // How often to publish own metrics
|
||||||
|
)
|
||||||
214
packages/relay/internal/core/core.go
Normal file
214
packages/relay/internal/core/core.go
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"relay/internal/common"
|
||||||
|
"relay/internal/shared"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/libp2p/go-libp2p"
|
||||||
|
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||||
|
"github.com/libp2p/go-libp2p/core/crypto"
|
||||||
|
"github.com/libp2p/go-libp2p/core/host"
|
||||||
|
"github.com/libp2p/go-libp2p/core/peer"
|
||||||
|
"github.com/libp2p/go-libp2p/p2p/protocol/ping"
|
||||||
|
"github.com/libp2p/go-libp2p/p2p/security/noise"
|
||||||
|
"github.com/libp2p/go-libp2p/p2p/transport/tcp"
|
||||||
|
ws "github.com/libp2p/go-libp2p/p2p/transport/websocket"
|
||||||
|
"github.com/multiformats/go-multiaddr"
|
||||||
|
"github.com/oklog/ulid/v2"
|
||||||
|
"github.com/pion/webrtc/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// -- Variables --
|
||||||
|
|
||||||
|
var globalRelay *Relay
|
||||||
|
|
||||||
|
// -- Structs --
|
||||||
|
|
||||||
|
// RelayInfo contains light information of Relay, in mesh-friendly format
|
||||||
|
type RelayInfo struct {
|
||||||
|
ID peer.ID
|
||||||
|
MeshAddrs []string // Addresses of this relay
|
||||||
|
MeshRooms *common.SafeMap[string, shared.RoomInfo] // Rooms hosted by this relay
|
||||||
|
MeshLatencies *common.SafeMap[string, time.Duration] // Latencies to other peers from this relay
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relay structure enhanced with metrics and state
|
||||||
|
type Relay struct {
|
||||||
|
RelayInfo
|
||||||
|
|
||||||
|
Host host.Host // libp2p host for peer-to-peer networking
|
||||||
|
PubSub *pubsub.PubSub // PubSub for state synchronization
|
||||||
|
PingService *ping.PingService
|
||||||
|
|
||||||
|
// Local
|
||||||
|
LocalRooms *common.SafeMap[ulid.ULID, *shared.Room] // room ID -> local Room struct (hosted by this relay)
|
||||||
|
LocalMeshPeers *common.SafeMap[peer.ID, *RelayInfo] // peer ID -> mesh peer relay info (connected to this relay)
|
||||||
|
LocalMeshConnections *common.SafeMap[peer.ID, *webrtc.PeerConnection] // peer ID -> PeerConnection (connected to this relay)
|
||||||
|
|
||||||
|
// Protocols
|
||||||
|
ProtocolRegistry
|
||||||
|
|
||||||
|
// PubSub Topics
|
||||||
|
pubTopicState *pubsub.Topic // topic for room states
|
||||||
|
pubTopicRelayMetrics *pubsub.Topic // topic for relay metrics/status
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRelay(ctx context.Context, port int, identityKey crypto.PrivKey) (*Relay, error) {
|
||||||
|
listenAddrs := []string{
|
||||||
|
fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", port), // IPv4 - Raw TCP
|
||||||
|
fmt.Sprintf("/ip6/::/tcp/%d", port), // IPv6 - Raw TCP
|
||||||
|
fmt.Sprintf("/ip4/0.0.0.0/tcp/%d/ws", port), // IPv4 - TCP WebSocket
|
||||||
|
fmt.Sprintf("/ip6/::/tcp/%d/ws", port), // IPv6 - TCP WebSocket
|
||||||
|
}
|
||||||
|
|
||||||
|
var muAddrs []multiaddr.Multiaddr
|
||||||
|
for _, addr := range listenAddrs {
|
||||||
|
multiAddr, err := multiaddr.NewMultiaddr(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse multiaddr '%s': %w", addr, err)
|
||||||
|
}
|
||||||
|
muAddrs = append(muAddrs, multiAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize libp2p host
|
||||||
|
p2pHost, err := libp2p.New(
|
||||||
|
// TODO: Currently static identity
|
||||||
|
libp2p.Identity(identityKey),
|
||||||
|
// Enable required transports
|
||||||
|
libp2p.Transport(tcp.NewTCPTransport),
|
||||||
|
libp2p.Transport(ws.New),
|
||||||
|
// Other options
|
||||||
|
libp2p.ListenAddrs(muAddrs...),
|
||||||
|
libp2p.Security(noise.ID, noise.New),
|
||||||
|
libp2p.EnableRelay(),
|
||||||
|
libp2p.EnableHolePunching(),
|
||||||
|
libp2p.EnableNATService(),
|
||||||
|
libp2p.EnableAutoNATv2(),
|
||||||
|
libp2p.ShareTCPListener(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create libp2p host for relay: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up pubsub
|
||||||
|
p2pPubsub, err := pubsub.NewGossipSub(ctx, p2pHost)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create pubsub: %w, addrs: %v", err, p2pHost.Addrs())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Ping Service
|
||||||
|
pingSvc := ping.NewPingService(p2pHost)
|
||||||
|
|
||||||
|
var addresses []string
|
||||||
|
for _, addr := range p2pHost.Addrs() {
|
||||||
|
addresses = append(addresses, addr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &Relay{
|
||||||
|
RelayInfo: RelayInfo{
|
||||||
|
ID: p2pHost.ID(),
|
||||||
|
MeshAddrs: addresses,
|
||||||
|
MeshRooms: common.NewSafeMap[string, shared.RoomInfo](),
|
||||||
|
MeshLatencies: common.NewSafeMap[string, time.Duration](),
|
||||||
|
},
|
||||||
|
Host: p2pHost,
|
||||||
|
PubSub: p2pPubsub,
|
||||||
|
PingService: pingSvc,
|
||||||
|
LocalRooms: common.NewSafeMap[ulid.ULID, *shared.Room](),
|
||||||
|
LocalMeshPeers: common.NewSafeMap[peer.ID, *RelayInfo](),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add network notifier after relay is initialized
|
||||||
|
p2pHost.Network().Notify(&networkNotifier{relay: r})
|
||||||
|
|
||||||
|
// Set up PubSub topics and handlers
|
||||||
|
if err = r.setupPubSub(ctx); err != nil {
|
||||||
|
err = p2pHost.Close()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to close host after PubSub setup failure", "err", err)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to setup PubSub: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Protocol Registry
|
||||||
|
r.ProtocolRegistry = NewProtocolRegistry(r)
|
||||||
|
|
||||||
|
// Start discovery features
|
||||||
|
if err = startMDNSDiscovery(r); err != nil {
|
||||||
|
slog.Warn("Failed to initialize mDNS discovery, continuing without..", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start background tasks
|
||||||
|
go r.periodicMetricsPublisher(ctx)
|
||||||
|
|
||||||
|
printConnectInstructions(p2pHost)
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitRelay(ctx context.Context, ctxCancel context.CancelFunc) error {
|
||||||
|
var err error
|
||||||
|
persistentDir := common.GetFlags().PersistDir
|
||||||
|
|
||||||
|
// Load or generate identity key
|
||||||
|
var identityKey crypto.PrivKey
|
||||||
|
var privKey ed25519.PrivateKey
|
||||||
|
// First check if we need to generate identity
|
||||||
|
hasIdentity := len(persistentDir) > 0 && common.GetFlags().RegenIdentity == false
|
||||||
|
if hasIdentity {
|
||||||
|
_, err = os.Stat(persistentDir + "/identity.key")
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("failed to check identity key file: %w", err)
|
||||||
|
} else if os.IsNotExist(err) {
|
||||||
|
hasIdentity = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasIdentity {
|
||||||
|
// Make sure the persistent directory exists
|
||||||
|
if err = os.MkdirAll(persistentDir, 0700); err != nil {
|
||||||
|
return fmt.Errorf("failed to create persistent data directory: %w", err)
|
||||||
|
}
|
||||||
|
// Generate
|
||||||
|
slog.Info("Generating new identity for relay")
|
||||||
|
privKey, err = common.GenerateED25519Key()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to generate new identity: %w", err)
|
||||||
|
}
|
||||||
|
// Save the key
|
||||||
|
if err = common.SaveED25519Key(privKey, persistentDir+"/identity.key"); err != nil {
|
||||||
|
return fmt.Errorf("failed to save identity key: %w", err)
|
||||||
|
}
|
||||||
|
slog.Info("New identity generated and saved", "path", persistentDir+"/identity.key")
|
||||||
|
} else {
|
||||||
|
slog.Info("Loading existing identity for relay", "path", persistentDir+"/identity.key")
|
||||||
|
// Load the key
|
||||||
|
privKey, err = common.LoadED25519Key(persistentDir + "/identity.key")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load identity key: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to libp2p crypto.PrivKey
|
||||||
|
identityKey, err = crypto.UnmarshalEd25519PrivateKey(privKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal ED25519 private key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
globalRelay, err = NewRelay(ctx, common.GetFlags().EndpointPort, identityKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create relay: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = common.InitWebRTCAPI(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("Relay initialized", "id", globalRelay.ID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
38
packages/relay/internal/core/mdns.go
Normal file
38
packages/relay/internal/core/mdns.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"github.com/libp2p/go-libp2p/core/peer"
|
||||||
|
"github.com/libp2p/go-libp2p/p2p/discovery/mdns"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
mdnsDiscoveryRendezvous = "/nestri-relay/mdns-discovery/1.0.0" // Shared string for mDNS discovery
|
||||||
|
)
|
||||||
|
|
||||||
|
type discoveryNotifee struct {
|
||||||
|
relay *Relay
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *discoveryNotifee) HandlePeerFound(pi peer.AddrInfo) {
|
||||||
|
if d.relay != nil {
|
||||||
|
if err := d.relay.connectToRelay(context.Background(), &pi); err != nil {
|
||||||
|
slog.Error("failed to connect to discovered relay", "peer", pi.ID, "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startMDNSDiscovery(relay *Relay) error {
|
||||||
|
d := &discoveryNotifee{
|
||||||
|
relay: relay,
|
||||||
|
}
|
||||||
|
|
||||||
|
service := mdns.NewMdnsService(relay.Host, mdnsDiscoveryRendezvous, d)
|
||||||
|
if err := service.Start(); err != nil {
|
||||||
|
return fmt.Errorf("failed to start mDNS discovery: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
128
packages/relay/internal/core/metrics.go
Normal file
128
packages/relay/internal/core/metrics.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/libp2p/go-libp2p/core/peer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- Metrics Collection and Publishing ---
|
||||||
|
|
||||||
|
// periodicMetricsPublisher periodically gathers local metrics and publishes them.
|
||||||
|
func (r *Relay) periodicMetricsPublisher(ctx context.Context) {
|
||||||
|
ticker := time.NewTicker(metricsPublishInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
// Publish immediately on start
|
||||||
|
if err := r.publishRelayMetrics(ctx); err != nil {
|
||||||
|
slog.Error("Failed to publish initial relay metrics", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
slog.Info("Stopping metrics publisher")
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
if err := r.publishRelayMetrics(ctx); err != nil {
|
||||||
|
slog.Error("Failed to publish relay metrics", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// publishRelayMetrics sends the current relay status to the mesh.
|
||||||
|
func (r *Relay) publishRelayMetrics(ctx context.Context) error {
|
||||||
|
if r.pubTopicRelayMetrics == nil {
|
||||||
|
slog.Warn("Cannot publish relay metrics: topic is nil")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check all peer latencies
|
||||||
|
r.checkAllPeerLatencies(ctx)
|
||||||
|
|
||||||
|
data, err := json.Marshal(r.RelayInfo)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal relay status: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pubErr := r.pubTopicRelayMetrics.Publish(ctx, data); pubErr != nil {
|
||||||
|
// Don't return error on publish failure, just log
|
||||||
|
slog.Error("Failed to publish relay metrics message", "err", pubErr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkAllPeerLatencies measures latency to all currently connected peers.
|
||||||
|
func (r *Relay) checkAllPeerLatencies(ctx context.Context) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for _, p := range r.Host.Network().Peers() {
|
||||||
|
if p == r.ID {
|
||||||
|
continue // Skip self
|
||||||
|
}
|
||||||
|
wg.Add(1)
|
||||||
|
// Run checks concurrently
|
||||||
|
go func(peerID peer.ID) {
|
||||||
|
defer wg.Done()
|
||||||
|
go r.measureLatencyToPeer(ctx, peerID)
|
||||||
|
}(p)
|
||||||
|
}
|
||||||
|
wg.Wait() // Wait for all latency checks to complete
|
||||||
|
}
|
||||||
|
|
||||||
|
// measureLatencyToPeer pings a specific peer and updates the local latency map.
|
||||||
|
func (r *Relay) measureLatencyToPeer(ctx context.Context, peerID peer.ID) {
|
||||||
|
// Check peer status first
|
||||||
|
if !r.hasConnectedPeer(peerID) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a context for the ping operation
|
||||||
|
pingCtx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Use the PingService instance stored in the Relay struct
|
||||||
|
if r.PingService == nil {
|
||||||
|
slog.Error("PingService is nil, cannot measure latency", "peer", peerID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resultsCh := r.PingService.Ping(pingCtx, peerID)
|
||||||
|
|
||||||
|
// Wait for the result (or timeout)
|
||||||
|
select {
|
||||||
|
case <-pingCtx.Done():
|
||||||
|
// Ping timed out
|
||||||
|
slog.Warn("Latency check canceled", "peer", peerID, "err", pingCtx.Err())
|
||||||
|
case result, ok := <-resultsCh:
|
||||||
|
if !ok {
|
||||||
|
// Channel closed unexpectedly
|
||||||
|
slog.Warn("Ping service channel closed unexpectedly", "peer", peerID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Received ping result
|
||||||
|
if result.Error != nil {
|
||||||
|
slog.Warn("Latency check failed, removing peer from local peers map", "peer", peerID, "err", result.Error)
|
||||||
|
// Remove from MeshPeers if ping failed
|
||||||
|
if r.LocalMeshPeers.Has(peerID) {
|
||||||
|
r.LocalMeshPeers.Delete(peerID)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping successful, update latency
|
||||||
|
latency := result.RTT
|
||||||
|
// Ensure latency is not zero if successful, assign a minimal value if so.
|
||||||
|
// Sometimes RTT can be reported as 0 for very fast local connections.
|
||||||
|
if latency <= 0 {
|
||||||
|
latency = 1 * time.Microsecond
|
||||||
|
}
|
||||||
|
|
||||||
|
r.RelayInfo.MeshLatencies.Set(peerID.String(), latency)
|
||||||
|
}
|
||||||
|
}
|
||||||
128
packages/relay/internal/core/p2p.go
Normal file
128
packages/relay/internal/core/p2p.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/libp2p/go-libp2p/core/host"
|
||||||
|
"github.com/libp2p/go-libp2p/core/network"
|
||||||
|
"github.com/libp2p/go-libp2p/core/peer"
|
||||||
|
"github.com/multiformats/go-multiaddr"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- Structs ---
|
||||||
|
|
||||||
|
// networkNotifier logs connection events and updates relay state
|
||||||
|
type networkNotifier struct {
|
||||||
|
relay *Relay
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connected is called when a connection is established
|
||||||
|
func (n *networkNotifier) Connected(net network.Network, conn network.Conn) {
|
||||||
|
if n.relay == nil {
|
||||||
|
n.relay.onPeerConnected(conn.RemotePeer())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disconnected is called when a connection is terminated
|
||||||
|
func (n *networkNotifier) Disconnected(net network.Network, conn network.Conn) {
|
||||||
|
// Update the status of the disconnected peer
|
||||||
|
if n.relay != nil {
|
||||||
|
n.relay.onPeerDisconnected(conn.RemotePeer())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen is called when the node starts listening on an address
|
||||||
|
func (n *networkNotifier) Listen(net network.Network, addr multiaddr.Multiaddr) {}
|
||||||
|
|
||||||
|
// ListenClose is called when the node stops listening on an address
|
||||||
|
func (n *networkNotifier) ListenClose(net network.Network, addr multiaddr.Multiaddr) {}
|
||||||
|
|
||||||
|
// --- PubSub Setup ---
|
||||||
|
|
||||||
|
// setupPubSub initializes PubSub topics and subscriptions.
|
||||||
|
func (r *Relay) setupPubSub(ctx context.Context) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Room State Topic
|
||||||
|
r.pubTopicState, err = r.PubSub.Join(roomStateTopicName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to join room state topic '%s': %w", roomStateTopicName, err)
|
||||||
|
}
|
||||||
|
stateSub, err := r.pubTopicState.Subscribe()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to subscribe to room state topic '%s': %w", roomStateTopicName, err)
|
||||||
|
}
|
||||||
|
go r.handleRoomStateMessages(ctx, stateSub) // Handler in relay_state.go
|
||||||
|
|
||||||
|
// Relay Metrics Topic
|
||||||
|
r.pubTopicRelayMetrics, err = r.PubSub.Join(relayMetricsTopicName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to join relay metrics topic '%s': %w", relayMetricsTopicName, err)
|
||||||
|
}
|
||||||
|
metricsSub, err := r.pubTopicRelayMetrics.Subscribe()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to subscribe to relay metrics topic '%s': %w", relayMetricsTopicName, err)
|
||||||
|
}
|
||||||
|
go r.handleRelayMetricsMessages(ctx, metricsSub) // Handler in relay_state.go
|
||||||
|
|
||||||
|
slog.Info("PubSub topics joined and subscriptions started")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Connection Management ---
|
||||||
|
|
||||||
|
// connectToRelay is internal method to connect to a relay peer using multiaddresses
|
||||||
|
func (r *Relay) connectToRelay(ctx context.Context, peerInfo *peer.AddrInfo) error {
|
||||||
|
if peerInfo.ID == r.ID {
|
||||||
|
return errors.New("cannot connect to self")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a timeout for the connection attempt
|
||||||
|
connectCtx, cancel := context.WithTimeout(ctx, 15*time.Second) // 15s timeout
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
slog.Info("Attempting to connect to peer", "peer", peerInfo.ID, "addrs", peerInfo.Addrs)
|
||||||
|
if err := r.Host.Connect(connectCtx, *peerInfo); err != nil {
|
||||||
|
return fmt.Errorf("failed to connect to %s: %w", peerInfo.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("Successfully connected to peer", "peer", peerInfo.ID, "addrs", peerInfo.Addrs)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectToRelay connects to another relay by its multiaddress.
|
||||||
|
func (r *Relay) ConnectToRelay(ctx context.Context, addr string) error {
|
||||||
|
ma, err := multiaddr.NewMultiaddr(addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid multiaddress: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
peerInfo, err := peer.AddrInfoFromP2pAddr(ma)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to extract peer info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.connectToRelay(ctx, peerInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// printConnectInstructions logs the multiaddresses for connecting to this relay.
|
||||||
|
func printConnectInstructions(p2pHost host.Host) {
|
||||||
|
peerInfo := peer.AddrInfo{
|
||||||
|
ID: p2pHost.ID(),
|
||||||
|
Addrs: p2pHost.Addrs(),
|
||||||
|
}
|
||||||
|
addrs, err := peer.AddrInfoToP2pAddrs(&peerInfo)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to convert peer info to addresses", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("Mesh connection addresses:")
|
||||||
|
for _, addr := range addrs {
|
||||||
|
slog.Info(fmt.Sprintf("> %s", addr.String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
694
packages/relay/internal/core/protocol_stream.go
Normal file
694
packages/relay/internal/core/protocol_stream.go
Normal file
@@ -0,0 +1,694 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"relay/internal/common"
|
||||||
|
"relay/internal/connections"
|
||||||
|
"relay/internal/shared"
|
||||||
|
|
||||||
|
"github.com/libp2p/go-libp2p/core/network"
|
||||||
|
"github.com/libp2p/go-libp2p/core/peer"
|
||||||
|
"github.com/pion/rtp"
|
||||||
|
"github.com/pion/webrtc/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO:s
|
||||||
|
// TODO: When disconnecting with stream open, causes crash on requester
|
||||||
|
// TODO: Need to trigger stream request if remote room is online and there are participants in local waiting
|
||||||
|
// TODO: Cleanup local room state when stream is closed upstream
|
||||||
|
|
||||||
|
// --- Protocol IDs ---
|
||||||
|
const (
|
||||||
|
protocolStreamRequest = "/nestri-relay/stream-request/1.0.0" // For requesting a stream from relay
|
||||||
|
protocolStreamPush = "/nestri-relay/stream-push/1.0.0" // For pushing a stream to relay
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- Protocol Types ---
|
||||||
|
|
||||||
|
// StreamConnection is a connection between two relays for stream protocol
|
||||||
|
type StreamConnection struct {
|
||||||
|
pc *webrtc.PeerConnection
|
||||||
|
ndc *connections.NestriDataChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamProtocol deals with meshed stream forwarding
|
||||||
|
type StreamProtocol struct {
|
||||||
|
relay *Relay
|
||||||
|
servedConns *common.SafeMap[peer.ID, *StreamConnection] // peer ID -> StreamConnection (for served streams)
|
||||||
|
incomingConns *common.SafeMap[string, *StreamConnection] // room name -> StreamConnection (for incoming pushed streams)
|
||||||
|
requestedConns *common.SafeMap[string, *StreamConnection] // room name -> StreamConnection (for requested streams from other relays)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStreamProtocol(relay *Relay) *StreamProtocol {
|
||||||
|
protocol := &StreamProtocol{
|
||||||
|
relay: relay,
|
||||||
|
servedConns: common.NewSafeMap[peer.ID, *StreamConnection](),
|
||||||
|
incomingConns: common.NewSafeMap[string, *StreamConnection](),
|
||||||
|
requestedConns: common.NewSafeMap[string, *StreamConnection](),
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol.relay.Host.SetStreamHandler(protocolStreamRequest, protocol.handleStreamRequest)
|
||||||
|
protocol.relay.Host.SetStreamHandler(protocolStreamPush, protocol.handleStreamPush)
|
||||||
|
|
||||||
|
return protocol
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Protocol Stream Handlers ---
|
||||||
|
|
||||||
|
// handleStreamRequest manages a request from another relay for a stream hosted locally
|
||||||
|
func (sp *StreamProtocol) handleStreamRequest(stream network.Stream) {
|
||||||
|
brw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))
|
||||||
|
safeBRW := common.NewSafeBufioRW(brw)
|
||||||
|
|
||||||
|
iceHolder := make([]webrtc.ICECandidateInit, 0)
|
||||||
|
for {
|
||||||
|
data, err := safeBRW.Receive()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) || errors.Is(err, network.ErrReset) {
|
||||||
|
slog.Debug("Stream request connection closed by peer", "peer", stream.Conn().RemotePeer())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Error("Failed to receive data", "err", err)
|
||||||
|
_ = stream.Reset()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseMsg connections.MessageBase
|
||||||
|
if err = json.Unmarshal(data, &baseMsg); err != nil {
|
||||||
|
slog.Error("Failed to unmarshal base message", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch baseMsg.Type {
|
||||||
|
case "request-stream-room":
|
||||||
|
var rawMsg connections.MessageRaw
|
||||||
|
if err = json.Unmarshal(data, &rawMsg); err != nil {
|
||||||
|
slog.Error("Failed to unmarshal raw message for room stream request", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var roomName string
|
||||||
|
if err = json.Unmarshal(rawMsg.Data, &roomName); err != nil {
|
||||||
|
slog.Error("Failed to unmarshal room name from raw message", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("Received stream request for room", "room", roomName)
|
||||||
|
room := sp.relay.GetRoomByName(roomName)
|
||||||
|
if room == nil || !room.IsOnline() || room.OwnerID != sp.relay.ID {
|
||||||
|
// TODO: Allow forward requests to other relays from here?
|
||||||
|
slog.Debug("Cannot provide stream for nil, offline or non-owned room", "room", roomName, "is_online", room != nil && room.IsOnline(), "is_owner", room != nil && room.OwnerID == sp.relay.ID)
|
||||||
|
// Respond with "request-stream-offline" message with room name
|
||||||
|
// TODO: Store the peer and send "online" message when the room comes online
|
||||||
|
roomNameData, err := json.Marshal(roomName)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to marshal room name for request stream offline", "room", roomName, "err", err)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
if err = safeBRW.SendJSON(connections.NewMessageRaw(
|
||||||
|
"request-stream-offline",
|
||||||
|
roomNameData,
|
||||||
|
)); err != nil {
|
||||||
|
slog.Error("Failed to send request stream offline message", "room", roomName, "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pc, err := common.CreatePeerConnection(func() {
|
||||||
|
slog.Info("PeerConnection closed for requested stream", "room", roomName)
|
||||||
|
// Cleanup the stream connection
|
||||||
|
if ok := sp.servedConns.Has(stream.Conn().RemotePeer()); ok {
|
||||||
|
sp.servedConns.Delete(stream.Conn().RemotePeer())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to create PeerConnection for requested stream", "room", roomName, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add tracks
|
||||||
|
if room.AudioTrack != nil {
|
||||||
|
if _, err = pc.AddTrack(room.AudioTrack); err != nil {
|
||||||
|
slog.Error("Failed to add audio track for requested stream", "room", roomName, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if room.VideoTrack != nil {
|
||||||
|
if _, err = pc.AddTrack(room.VideoTrack); err != nil {
|
||||||
|
slog.Error("Failed to add video track for requested stream", "room", roomName, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataChannel setup
|
||||||
|
settingOrdered := true
|
||||||
|
settingMaxRetransmits := uint16(2)
|
||||||
|
dc, err := pc.CreateDataChannel("relay-data", &webrtc.DataChannelInit{
|
||||||
|
Ordered: &settingOrdered,
|
||||||
|
MaxRetransmits: &settingMaxRetransmits,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to create DataChannel for requested stream", "room", roomName, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ndc := connections.NewNestriDataChannel(dc)
|
||||||
|
|
||||||
|
ndc.RegisterOnOpen(func() {
|
||||||
|
slog.Debug("Relay DataChannel opened for requested stream", "room", roomName)
|
||||||
|
})
|
||||||
|
ndc.RegisterOnClose(func() {
|
||||||
|
slog.Debug("Relay DataChannel closed for requested stream", "room", roomName)
|
||||||
|
})
|
||||||
|
ndc.RegisterMessageCallback("input", func(data []byte) {
|
||||||
|
if room.DataChannel != nil {
|
||||||
|
if err = room.DataChannel.SendBinary(data); err != nil {
|
||||||
|
slog.Error("Failed to forward input message from mesh to upstream room", "room", roomName, "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// ICE Candidate handling
|
||||||
|
pc.OnICECandidate(func(candidate *webrtc.ICECandidate) {
|
||||||
|
if candidate == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = safeBRW.SendJSON(connections.NewMessageICE("ice-candidate", candidate.ToJSON())); err != nil {
|
||||||
|
slog.Error("Failed to send ICE candidate message for requested stream", "room", roomName, "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create offer
|
||||||
|
offer, err := pc.CreateOffer(nil)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to create offer for requested stream", "room", roomName, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err = pc.SetLocalDescription(offer); err != nil {
|
||||||
|
slog.Error("Failed to set local description for requested stream", "room", roomName, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err = safeBRW.SendJSON(connections.NewMessageSDP("offer", offer)); err != nil {
|
||||||
|
slog.Error("Failed to send offer for requested stream", "room", roomName, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the connection
|
||||||
|
sp.servedConns.Set(stream.Conn().RemotePeer(), &StreamConnection{
|
||||||
|
pc: pc,
|
||||||
|
ndc: ndc,
|
||||||
|
})
|
||||||
|
|
||||||
|
slog.Debug("Sent offer for requested stream")
|
||||||
|
case "ice-candidate":
|
||||||
|
var iceMsg connections.MessageICE
|
||||||
|
if err := json.Unmarshal(data, &iceMsg); err != nil {
|
||||||
|
slog.Error("Failed to unmarshal ICE message", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if conn, ok := sp.servedConns.Get(stream.Conn().RemotePeer()); ok && conn.pc.RemoteDescription() != nil {
|
||||||
|
if err := conn.pc.AddICECandidate(iceMsg.Candidate); err != nil {
|
||||||
|
slog.Error("Failed to add ICE candidate", "err", err)
|
||||||
|
}
|
||||||
|
for _, heldIce := range iceHolder {
|
||||||
|
if err := conn.pc.AddICECandidate(heldIce); err != nil {
|
||||||
|
slog.Error("Failed to add held ICE candidate", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Clear the held candidates
|
||||||
|
iceHolder = make([]webrtc.ICECandidateInit, 0)
|
||||||
|
} else {
|
||||||
|
// Hold the candidate until remote description is set
|
||||||
|
iceHolder = append(iceHolder, iceMsg.Candidate)
|
||||||
|
}
|
||||||
|
case "answer":
|
||||||
|
var answerMsg connections.MessageSDP
|
||||||
|
if err := json.Unmarshal(data, &answerMsg); err != nil {
|
||||||
|
slog.Error("Failed to unmarshal answer from signaling message", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if conn, ok := sp.servedConns.Get(stream.Conn().RemotePeer()); ok {
|
||||||
|
if err := conn.pc.SetRemoteDescription(answerMsg.SDP); err != nil {
|
||||||
|
slog.Error("Failed to set remote description for answer", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
slog.Debug("Set remote description for answer")
|
||||||
|
} else {
|
||||||
|
slog.Warn("Received answer without active PeerConnection")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// requestStream manages the internals of the stream request
|
||||||
|
func (sp *StreamProtocol) requestStream(stream network.Stream, room *shared.Room) error {
|
||||||
|
brw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))
|
||||||
|
safeBRW := common.NewSafeBufioRW(brw)
|
||||||
|
|
||||||
|
slog.Debug("Requesting room stream from peer", "room", room.Name, "peer", stream.Conn().RemotePeer())
|
||||||
|
|
||||||
|
// Send room name to the remote peer
|
||||||
|
roomData, err := json.Marshal(room.Name)
|
||||||
|
if err != nil {
|
||||||
|
_ = stream.Close()
|
||||||
|
return fmt.Errorf("failed to marshal room name: %w", err)
|
||||||
|
}
|
||||||
|
if err = safeBRW.SendJSON(connections.NewMessageRaw(
|
||||||
|
"request-stream-room",
|
||||||
|
roomData,
|
||||||
|
)); err != nil {
|
||||||
|
_ = stream.Close()
|
||||||
|
return fmt.Errorf("failed to send room request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pc, err := common.CreatePeerConnection(func() {
|
||||||
|
slog.Info("Relay PeerConnection closed for requested stream", "room", room.Name)
|
||||||
|
_ = stream.Close() // ignore error as may be closed already
|
||||||
|
// Cleanup the stream connection
|
||||||
|
if ok := sp.requestedConns.Has(room.Name); ok {
|
||||||
|
sp.requestedConns.Delete(room.Name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
_ = stream.Close()
|
||||||
|
return fmt.Errorf("failed to create PeerConnection: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pc.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
|
||||||
|
localTrack, _ := webrtc.NewTrackLocalStaticRTP(track.Codec().RTPCodecCapability, track.ID(), "relay-"+room.Name+"-"+track.Kind().String())
|
||||||
|
slog.Debug("Received track for requested stream", "room", room.Name, "track_kind", track.Kind().String())
|
||||||
|
|
||||||
|
room.SetTrack(track.Kind(), localTrack)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
rtpPacket, _, err := track.ReadRTP()
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, io.EOF) {
|
||||||
|
slog.Error("Failed to read RTP packet for requested stream room", "room", room.Name, "err", err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
err = localTrack.WriteRTP(rtpPacket)
|
||||||
|
if err != nil && !errors.Is(err, io.ErrClosedPipe) {
|
||||||
|
slog.Error("Failed to write RTP to local track for requested stream room", "room", room.Name, "err", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
|
||||||
|
pc.OnDataChannel(func(dc *webrtc.DataChannel) {
|
||||||
|
ndc := connections.NewNestriDataChannel(dc)
|
||||||
|
ndc.RegisterOnOpen(func() {
|
||||||
|
slog.Debug("Relay DataChannel opened for requested stream", "room", room.Name)
|
||||||
|
})
|
||||||
|
ndc.RegisterOnClose(func() {
|
||||||
|
slog.Debug("Relay DataChannel closed for requested stream", "room", room.Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set the DataChannel in the requestedConns map
|
||||||
|
if conn, ok := sp.requestedConns.Get(room.Name); ok {
|
||||||
|
conn.ndc = ndc
|
||||||
|
} else {
|
||||||
|
sp.requestedConns.Set(room.Name, &StreamConnection{
|
||||||
|
pc: pc,
|
||||||
|
ndc: ndc,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// We do not handle any messages from upstream here
|
||||||
|
})
|
||||||
|
|
||||||
|
pc.OnICECandidate(func(candidate *webrtc.ICECandidate) {
|
||||||
|
if candidate == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = safeBRW.SendJSON(connections.NewMessageICE(
|
||||||
|
"ice-candidate",
|
||||||
|
candidate.ToJSON(),
|
||||||
|
)); err != nil {
|
||||||
|
slog.Error("Failed to send ICE candidate message for requested stream", "room", room.Name, "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Handle incoming messages (offer and candidates)
|
||||||
|
go func() {
|
||||||
|
iceHolder := make([]webrtc.ICECandidateInit, 0)
|
||||||
|
|
||||||
|
for {
|
||||||
|
data, err := safeBRW.Receive()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) || errors.Is(err, network.ErrReset) {
|
||||||
|
slog.Debug("Connection for requested stream closed by peer", "room", room.Name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Error("Failed to receive data for requested stream", "room", room.Name, "err", err)
|
||||||
|
_ = stream.Reset()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseMsg connections.MessageBase
|
||||||
|
if err = json.Unmarshal(data, &baseMsg); err != nil {
|
||||||
|
slog.Error("Failed to unmarshal base message for requested stream", "room", room.Name, "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch baseMsg.Type {
|
||||||
|
case "ice-candidate":
|
||||||
|
var iceMsg connections.MessageICE
|
||||||
|
if err = json.Unmarshal(data, &iceMsg); err != nil {
|
||||||
|
slog.Error("Failed to unmarshal ICE candidate for requested stream", "room", room.Name, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if conn, ok := sp.requestedConns.Get(room.Name); ok && conn.pc.RemoteDescription() != nil {
|
||||||
|
if err = conn.pc.AddICECandidate(iceMsg.Candidate); err != nil {
|
||||||
|
slog.Error("Failed to add ICE candidate for requested stream", "room", room.Name, "err", err)
|
||||||
|
}
|
||||||
|
// Add held candidates
|
||||||
|
for _, heldCandidate := range iceHolder {
|
||||||
|
if err = conn.pc.AddICECandidate(heldCandidate); err != nil {
|
||||||
|
slog.Error("Failed to add held ICE candidate for requested stream", "room", room.Name, "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Clear the held candidates
|
||||||
|
iceHolder = make([]webrtc.ICECandidateInit, 0)
|
||||||
|
} else {
|
||||||
|
// Hold the candidate until remote description is set
|
||||||
|
iceHolder = append(iceHolder, iceMsg.Candidate)
|
||||||
|
}
|
||||||
|
case "offer":
|
||||||
|
var offerMsg connections.MessageSDP
|
||||||
|
if err = json.Unmarshal(data, &offerMsg); err != nil {
|
||||||
|
slog.Error("Failed to unmarshal offer for requested stream", "room", room.Name, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err = pc.SetRemoteDescription(offerMsg.SDP); err != nil {
|
||||||
|
slog.Error("Failed to set remote description for requested stream", "room", room.Name, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
answer, err := pc.CreateAnswer(nil)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to create answer for requested stream", "room", room.Name, "err", err)
|
||||||
|
if err = stream.Reset(); err != nil {
|
||||||
|
slog.Error("Failed to reset stream for requested stream", "err", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = pc.SetLocalDescription(answer); err != nil {
|
||||||
|
slog.Error("Failed to set local description for requested stream", "room", room.Name, "err", err)
|
||||||
|
if err = stream.Reset(); err != nil {
|
||||||
|
slog.Error("Failed to reset stream for requested stream", "err", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = safeBRW.SendJSON(connections.NewMessageSDP(
|
||||||
|
"answer",
|
||||||
|
answer,
|
||||||
|
)); err != nil {
|
||||||
|
slog.Error("Failed to send answer for requested stream", "room", room.Name, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the connection
|
||||||
|
sp.requestedConns.Set(room.Name, &StreamConnection{
|
||||||
|
pc: pc,
|
||||||
|
ndc: nil,
|
||||||
|
})
|
||||||
|
|
||||||
|
slog.Debug("Sent answer for requested stream", "room", room.Name)
|
||||||
|
default:
|
||||||
|
slog.Warn("Unknown signaling message type", "room", room.Name, "type", baseMsg.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleStreamPush manages a stream push from a node (nestri-server)
|
||||||
|
func (sp *StreamProtocol) handleStreamPush(stream network.Stream) {
|
||||||
|
brw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))
|
||||||
|
safeBRW := common.NewSafeBufioRW(brw)
|
||||||
|
|
||||||
|
var room *shared.Room
|
||||||
|
iceHolder := make([]webrtc.ICECandidateInit, 0)
|
||||||
|
for {
|
||||||
|
data, err := safeBRW.Receive()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) || errors.Is(err, network.ErrReset) {
|
||||||
|
slog.Debug("Stream push connection closed by peer", "peer", stream.Conn().RemotePeer())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Error("Failed to receive data for stream push", "err", err)
|
||||||
|
_ = stream.Reset()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseMsg connections.MessageBase
|
||||||
|
if err = json.Unmarshal(data, &baseMsg); err != nil {
|
||||||
|
slog.Error("Failed to unmarshal base message from base message", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch baseMsg.Type {
|
||||||
|
case "push-stream-room":
|
||||||
|
var rawMsg connections.MessageRaw
|
||||||
|
if err = json.Unmarshal(data, &rawMsg); err != nil {
|
||||||
|
slog.Error("Failed to unmarshal room name from data", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var roomName string
|
||||||
|
if err = json.Unmarshal(rawMsg.Data, &roomName); err != nil {
|
||||||
|
slog.Error("Failed to unmarshal room name from raw message", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("Received stream push request for room", "room", roomName)
|
||||||
|
|
||||||
|
room = sp.relay.GetRoomByName(roomName)
|
||||||
|
if room != nil {
|
||||||
|
if room.OwnerID != sp.relay.ID {
|
||||||
|
slog.Error("Cannot push a stream to non-owned room", "room", room.Name, "owner_id", room.OwnerID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if room.IsOnline() {
|
||||||
|
slog.Error("Cannot push a stream to already online room", "room", room.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create a new room if it doesn't exist
|
||||||
|
room = sp.relay.CreateRoom(roomName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Respond with an OK with the room name
|
||||||
|
roomData, err := json.Marshal(room.Name)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to marshal room name for push stream response", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err = safeBRW.SendJSON(connections.NewMessageRaw(
|
||||||
|
"push-stream-ok",
|
||||||
|
roomData,
|
||||||
|
)); err != nil {
|
||||||
|
slog.Error("Failed to send push stream OK response", "room", room.Name, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case "ice-candidate":
|
||||||
|
var iceMsg connections.MessageICE
|
||||||
|
if err = json.Unmarshal(data, &iceMsg); err != nil {
|
||||||
|
slog.Error("Failed to unmarshal ICE candidate from data", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if conn, ok := sp.incomingConns.Get(room.Name); ok && conn.pc.RemoteDescription() != nil {
|
||||||
|
if err = conn.pc.AddICECandidate(iceMsg.Candidate); err != nil {
|
||||||
|
slog.Error("Failed to add ICE candidate for pushed stream", "err", err)
|
||||||
|
}
|
||||||
|
for _, heldIce := range iceHolder {
|
||||||
|
if err := conn.pc.AddICECandidate(heldIce); err != nil {
|
||||||
|
slog.Error("Failed to add held ICE candidate for pushed stream", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Clear the held candidates
|
||||||
|
iceHolder = make([]webrtc.ICECandidateInit, 0)
|
||||||
|
} else {
|
||||||
|
// Hold the candidate until remote description is set
|
||||||
|
iceHolder = append(iceHolder, iceMsg.Candidate)
|
||||||
|
}
|
||||||
|
case "offer":
|
||||||
|
// Make sure we have room set to push to (set by "push-stream-room")
|
||||||
|
if room == nil {
|
||||||
|
slog.Error("Received offer without room set for stream push")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var offerMsg connections.MessageSDP
|
||||||
|
if err = json.Unmarshal(data, &offerMsg); err != nil {
|
||||||
|
slog.Error("Failed to unmarshal offer from data", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create PeerConnection for the incoming stream
|
||||||
|
pc, err := common.CreatePeerConnection(func() {
|
||||||
|
slog.Info("PeerConnection closed for pushed stream", "room", room.Name)
|
||||||
|
// Cleanup the stream connection
|
||||||
|
if ok := sp.incomingConns.Has(room.Name); ok {
|
||||||
|
sp.incomingConns.Delete(room.Name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to create PeerConnection for pushed stream", "room", room.Name, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pc.OnDataChannel(func(dc *webrtc.DataChannel) {
|
||||||
|
// TODO: Is this the best way to handle DataChannel? Should we just use the map directly?
|
||||||
|
room.DataChannel = connections.NewNestriDataChannel(dc)
|
||||||
|
room.DataChannel.RegisterOnOpen(func() {
|
||||||
|
slog.Debug("DataChannel opened for pushed stream", "room", room.Name)
|
||||||
|
})
|
||||||
|
room.DataChannel.RegisterOnClose(func() {
|
||||||
|
slog.Debug("DataChannel closed for pushed stream", "room", room.Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set the DataChannel in the incomingConns map
|
||||||
|
if conn, ok := sp.incomingConns.Get(room.Name); ok {
|
||||||
|
conn.ndc = room.DataChannel
|
||||||
|
} else {
|
||||||
|
sp.incomingConns.Set(room.Name, &StreamConnection{
|
||||||
|
pc: pc,
|
||||||
|
ndc: room.DataChannel,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
pc.OnICECandidate(func(candidate *webrtc.ICECandidate) {
|
||||||
|
if candidate == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = safeBRW.SendJSON(connections.NewMessageICE(
|
||||||
|
"ice-candidate",
|
||||||
|
candidate.ToJSON(),
|
||||||
|
)); err != nil {
|
||||||
|
slog.Error("Failed to send ICE candidate message for pushed stream", "room", room.Name, "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
pc.OnTrack(func(remoteTrack *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
|
||||||
|
localTrack, err := webrtc.NewTrackLocalStaticRTP(remoteTrack.Codec().RTPCodecCapability, remoteTrack.Kind().String(), fmt.Sprintf("nestri-%s-%s", room.Name, remoteTrack.Kind().String()))
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to create local track for pushed stream", "room", room.Name, "track_kind", remoteTrack.Kind().String(), "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Debug("Received track for pushed stream", "room", room.Name, "track_kind", remoteTrack.Kind().String())
|
||||||
|
|
||||||
|
// Set track for Room
|
||||||
|
room.SetTrack(remoteTrack.Kind(), localTrack)
|
||||||
|
|
||||||
|
// Prepare PlayoutDelayExtension so we don't need to recreate it for each packet
|
||||||
|
playoutExt := &rtp.PlayoutDelayExtension{
|
||||||
|
MinDelay: 0,
|
||||||
|
MaxDelay: 0,
|
||||||
|
}
|
||||||
|
playoutPayload, err := playoutExt.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to marshal PlayoutDelayExtension for room", "room", room.Name, "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
rtpPacket, _, err := remoteTrack.ReadRTP()
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, io.EOF) {
|
||||||
|
slog.Error("Failed to read RTP from remote track for room", "room", room.Name, "err", err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use PlayoutDelayExtension for low latency, if set for this track kind
|
||||||
|
if extID, ok := common.GetExtension(remoteTrack.Kind(), common.ExtensionPlayoutDelay); ok {
|
||||||
|
if err := rtpPacket.SetExtension(extID, playoutPayload); err != nil {
|
||||||
|
slog.Error("Failed to set PlayoutDelayExtension for room", "room", room.Name, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = localTrack.WriteRTP(rtpPacket)
|
||||||
|
if err != nil && !errors.Is(err, io.ErrClosedPipe) {
|
||||||
|
slog.Error("Failed to write RTP to local track for room", "room", room.Name, "err", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Debug("Track closed for room", "room", room.Name, "track_kind", remoteTrack.Kind().String())
|
||||||
|
|
||||||
|
// Cleanup the track from the room
|
||||||
|
room.SetTrack(remoteTrack.Kind(), nil)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set the remote description
|
||||||
|
if err = pc.SetRemoteDescription(offerMsg.SDP); err != nil {
|
||||||
|
slog.Error("Failed to set remote description for pushed stream", "room", room.Name, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
slog.Debug("Set remote description for pushed stream", "room", room.Name)
|
||||||
|
|
||||||
|
// Create an answer
|
||||||
|
answer, err := pc.CreateAnswer(nil)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to create answer for pushed stream", "room", room.Name, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err = pc.SetLocalDescription(answer); err != nil {
|
||||||
|
slog.Error("Failed to set local description for pushed stream", "room", room.Name, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err = safeBRW.SendJSON(connections.NewMessageSDP(
|
||||||
|
"answer",
|
||||||
|
answer,
|
||||||
|
)); err != nil {
|
||||||
|
slog.Error("Failed to send answer for pushed stream", "room", room.Name, "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the connection
|
||||||
|
sp.incomingConns.Set(room.Name, &StreamConnection{
|
||||||
|
pc: pc,
|
||||||
|
ndc: room.DataChannel, // if it exists, if not it will be set later
|
||||||
|
})
|
||||||
|
slog.Debug("Sent answer for pushed stream", "room", room.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Public Usable Methods ---
|
||||||
|
|
||||||
|
// RequestStream sends a request to get room stream from another relay
|
||||||
|
func (sp *StreamProtocol) RequestStream(ctx context.Context, room *shared.Room, peerID peer.ID) error {
|
||||||
|
stream, err := sp.relay.Host.NewStream(ctx, peerID, protocolStreamRequest)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create stream request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sp.requestStream(stream, room)
|
||||||
|
}
|
||||||
13
packages/relay/internal/core/protocols.go
Normal file
13
packages/relay/internal/core/protocols.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
// ProtocolRegistry is a type holding all protocols to split away the bloat
|
||||||
|
type ProtocolRegistry struct {
|
||||||
|
StreamProtocol *StreamProtocol
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProtocolRegistry initializes and returns a new protocol registry
|
||||||
|
func NewProtocolRegistry(relay *Relay) ProtocolRegistry {
|
||||||
|
return ProtocolRegistry{
|
||||||
|
StreamProtocol: NewStreamProtocol(relay),
|
||||||
|
}
|
||||||
|
}
|
||||||
108
packages/relay/internal/core/room.go
Normal file
108
packages/relay/internal/core/room.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"relay/internal/shared"
|
||||||
|
|
||||||
|
"github.com/libp2p/go-libp2p/core/network"
|
||||||
|
"github.com/oklog/ulid/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- Room Management ---
|
||||||
|
|
||||||
|
// GetRoomByID retrieves a local Room struct by its ULID
|
||||||
|
func (r *Relay) GetRoomByID(id ulid.ULID) *shared.Room {
|
||||||
|
if room, ok := r.LocalRooms.Get(id); ok {
|
||||||
|
return room
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoomByName retrieves a local Room struct by its name
|
||||||
|
func (r *Relay) GetRoomByName(name string) *shared.Room {
|
||||||
|
for _, room := range r.LocalRooms.Copy() {
|
||||||
|
if room.Name == name {
|
||||||
|
return room
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRoom creates a new local Room struct with the given name
|
||||||
|
func (r *Relay) CreateRoom(name string) *shared.Room {
|
||||||
|
roomID := ulid.Make()
|
||||||
|
room := shared.NewRoom(name, roomID, r.ID)
|
||||||
|
r.LocalRooms.Set(room.ID, room)
|
||||||
|
slog.Debug("Created new local room", "room", name, "id", room.ID)
|
||||||
|
return room
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRoomIfEmpty checks if a local room struct is inactive and can be removed
|
||||||
|
func (r *Relay) DeleteRoomIfEmpty(room *shared.Room) {
|
||||||
|
if room == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if room.Participants.Len() == 0 && r.LocalRooms.Has(room.ID) {
|
||||||
|
slog.Debug("Deleting empty room without participants", "room", room.Name)
|
||||||
|
r.LocalRooms.Delete(room.ID)
|
||||||
|
err := room.PeerConnection.Close()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to close Room PeerConnection", "room", room.Name, "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRemoteRoomByName returns room from mesh by name
|
||||||
|
func (r *Relay) GetRemoteRoomByName(roomName string) *shared.RoomInfo {
|
||||||
|
for _, room := range r.MeshRooms.Copy() {
|
||||||
|
if room.Name == roomName && room.OwnerID != r.ID {
|
||||||
|
// Make sure connection is alive
|
||||||
|
if r.Host.Network().Connectedness(room.OwnerID) == network.Connected {
|
||||||
|
return &room
|
||||||
|
} else {
|
||||||
|
slog.Debug("Removing stale peer, owns a room without connection", "room", roomName, "peer", room.OwnerID)
|
||||||
|
r.onPeerDisconnected(room.OwnerID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- State Publishing ---
|
||||||
|
|
||||||
|
// publishRoomStates publishes the state of all rooms currently owned by *this* relay
|
||||||
|
func (r *Relay) publishRoomStates(ctx context.Context) error {
|
||||||
|
if r.pubTopicState == nil {
|
||||||
|
slog.Warn("Cannot publish room states: topic is nil")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var statesToPublish []shared.RoomInfo
|
||||||
|
r.LocalRooms.Range(func(id ulid.ULID, room *shared.Room) bool {
|
||||||
|
// Only publish state for rooms owned by this relay
|
||||||
|
if room.OwnerID == r.ID {
|
||||||
|
statesToPublish = append(statesToPublish, shared.RoomInfo{
|
||||||
|
ID: room.ID,
|
||||||
|
Name: room.Name,
|
||||||
|
OwnerID: r.ID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return true // Continue iteration
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(statesToPublish) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(statesToPublish)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal local room states: %w", err)
|
||||||
|
}
|
||||||
|
if pubErr := r.pubTopicState.Publish(ctx, data); pubErr != nil {
|
||||||
|
slog.Error("Failed to publish room states message", "err", pubErr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
173
packages/relay/internal/core/state.go
Normal file
173
packages/relay/internal/core/state.go
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"log/slog"
|
||||||
|
"relay/internal/shared"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||||
|
"github.com/libp2p/go-libp2p/core/network"
|
||||||
|
"github.com/libp2p/go-libp2p/core/peer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- PubSub Message Handlers ---
|
||||||
|
|
||||||
|
// handleRoomStateMessages processes incoming room state updates from peers.
|
||||||
|
func (r *Relay) handleRoomStateMessages(ctx context.Context, sub *pubsub.Subscription) {
|
||||||
|
slog.Debug("Starting room state message handler...")
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
slog.Info("Stopping room state message handler")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
msg, err := sub.Next(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, context.Canceled) || errors.Is(err, pubsub.ErrSubscriptionCancelled) || errors.Is(err, context.DeadlineExceeded) {
|
||||||
|
slog.Info("Room state subscription ended", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slog.Error("Error receiving room state message", "err", err)
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if msg.GetFrom() == r.Host.ID() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var states []shared.RoomInfo
|
||||||
|
if err := json.Unmarshal(msg.Data, &states); err != nil {
|
||||||
|
slog.Error("Failed to unmarshal room states", "from", msg.GetFrom(), "data_len", len(msg.Data), "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
r.updateMeshRoomStates(msg.GetFrom(), states)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleRelayMetricsMessages processes incoming status updates from peers.
|
||||||
|
func (r *Relay) handleRelayMetricsMessages(ctx context.Context, sub *pubsub.Subscription) {
|
||||||
|
slog.Debug("Starting relay metrics message handler...")
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
slog.Info("Stopping relay metrics message handler")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
msg, err := sub.Next(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, context.Canceled) || errors.Is(err, pubsub.ErrSubscriptionCancelled) || errors.Is(err, context.DeadlineExceeded) {
|
||||||
|
slog.Info("Relay metrics subscription ended", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slog.Error("Error receiving relay metrics message", "err", err)
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if msg.GetFrom() == r.Host.ID() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var info RelayInfo
|
||||||
|
if err := json.Unmarshal(msg.Data, &info); err != nil {
|
||||||
|
slog.Error("Failed to unmarshal relay status", "from", msg.GetFrom(), "data_len", len(msg.Data), "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if info.ID != msg.GetFrom() {
|
||||||
|
slog.Error("Peer ID mismatch in relay status", "expected", info.ID, "actual", msg.GetFrom())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r.onPeerStatus(info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- State Check Functions ---
|
||||||
|
// hasConnectedPeer checks if peer is in map and has a valid connection
|
||||||
|
func (r *Relay) hasConnectedPeer(peerID peer.ID) bool {
|
||||||
|
if _, ok := r.LocalMeshPeers.Get(peerID); !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r.Host.Network().Connectedness(peerID) != network.Connected {
|
||||||
|
slog.Debug("Peer not connected", "peer", peerID)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- State Change Functions ---
|
||||||
|
|
||||||
|
// onPeerStatus updates the status of a peer based on received metrics, adding local perspective
|
||||||
|
func (r *Relay) onPeerStatus(recvInfo RelayInfo) {
|
||||||
|
r.LocalMeshPeers.Set(recvInfo.ID, &recvInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// onPeerConnected is called when a new peer connects to the relay
|
||||||
|
func (r *Relay) onPeerConnected(peerID peer.ID) {
|
||||||
|
// Add to local peer map
|
||||||
|
r.LocalMeshPeers.Set(peerID, &RelayInfo{
|
||||||
|
ID: peerID,
|
||||||
|
})
|
||||||
|
|
||||||
|
slog.Info("Peer connected", "peer", peerID)
|
||||||
|
|
||||||
|
// Trigger immediate state exchange
|
||||||
|
go func() {
|
||||||
|
if err := r.publishRelayMetrics(context.Background()); err != nil {
|
||||||
|
slog.Error("Failed to publish relay metrics on connect", "err", err)
|
||||||
|
} else {
|
||||||
|
if err = r.publishRoomStates(context.Background()); err != nil {
|
||||||
|
slog.Error("Failed to publish room states on connect", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// onPeerDisconnected marks a peer as disconnected in our status view and removes latency info
|
||||||
|
func (r *Relay) onPeerDisconnected(peerID peer.ID) {
|
||||||
|
slog.Info("Mesh peer disconnected, deleting from local peer map", "peer", peerID)
|
||||||
|
// Remove peer from local mesh peers
|
||||||
|
if r.LocalMeshPeers.Has(peerID) {
|
||||||
|
r.LocalMeshPeers.Delete(peerID)
|
||||||
|
}
|
||||||
|
// Remove any rooms associated with this peer
|
||||||
|
if r.MeshRooms.Has(peerID.String()) {
|
||||||
|
r.MeshRooms.Delete(peerID.String())
|
||||||
|
}
|
||||||
|
// Remove any latencies associated with this peer
|
||||||
|
if r.LocalMeshPeers.Has(peerID) {
|
||||||
|
r.LocalMeshPeers.Delete(peerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: If any rooms were routed through this peer, handle that case
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateMeshRoomStates merges received room states into the MeshRooms map
|
||||||
|
// TODO: Wrap in another type with timestamp or another mechanism to avoid conflicts
|
||||||
|
func (r *Relay) updateMeshRoomStates(peerID peer.ID, states []shared.RoomInfo) {
|
||||||
|
for _, state := range states {
|
||||||
|
if state.OwnerID == r.ID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If previously did not exist, but does now, request a connection if participants exist for our room
|
||||||
|
existed := r.MeshRooms.Has(state.ID.String())
|
||||||
|
if !existed {
|
||||||
|
// Request connection to this peer if we have participants in our local room
|
||||||
|
if room, ok := r.LocalRooms.Get(state.ID); ok {
|
||||||
|
if room.Participants.Len() > 0 {
|
||||||
|
slog.Debug("Got new remote room state, we locally have participants for, requesting stream", "room_name", room.Name, "peer", peerID)
|
||||||
|
if err := r.StreamProtocol.RequestStream(context.Background(), room, peerID); err != nil {
|
||||||
|
slog.Error("Failed to request stream for new remote room state", "room_name", room.Name, "peer", peerID, "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.MeshRooms.Set(state.ID.String(), state)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/pion/webrtc/v4"
|
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
"log/slog"
|
|
||||||
"relay/internal/common"
|
|
||||||
"relay/internal/connections"
|
|
||||||
gen "relay/internal/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ParticipantHandler(participant *Participant, room *Room, relay *Relay) {
|
|
||||||
onPCClose := func() {
|
|
||||||
slog.Debug("Participant PeerConnection closed", "participant", participant.ID, "room", room.Name)
|
|
||||||
room.removeParticipantByID(participant.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
participant.PeerConnection, err = common.CreatePeerConnection(onPCClose)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to create participant PeerConnection", "participant", participant.ID, "room", room.Name, "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Data channel settings
|
|
||||||
settingOrdered := true
|
|
||||||
settingMaxRetransmits := uint16(0)
|
|
||||||
dc, err := participant.PeerConnection.CreateDataChannel("data", &webrtc.DataChannelInit{
|
|
||||||
Ordered: &settingOrdered,
|
|
||||||
MaxRetransmits: &settingMaxRetransmits,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to create data channel for participant", "participant", participant.ID, "room", room.Name, "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
participant.DataChannel = connections.NewNestriDataChannel(dc)
|
|
||||||
|
|
||||||
// Register channel opening handling
|
|
||||||
participant.DataChannel.RegisterOnOpen(func() {
|
|
||||||
slog.Debug("DataChannel opened for participant", "participant", participant.ID, "room", room.Name)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Register channel closing handling
|
|
||||||
participant.DataChannel.RegisterOnClose(func() {
|
|
||||||
slog.Debug("DataChannel closed for participant", "participant", participant.ID, "room", room.Name)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Register text message handling
|
|
||||||
participant.DataChannel.RegisterMessageCallback("input", func(data []byte) {
|
|
||||||
ForwardParticipantDataChannelMessage(participant, room, data)
|
|
||||||
})
|
|
||||||
|
|
||||||
participant.PeerConnection.OnICECandidate(func(candidate *webrtc.ICECandidate) {
|
|
||||||
if candidate == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := participant.WebSocket.SendICECandidateMessageWS(candidate.ToJSON()); err != nil {
|
|
||||||
slog.Error("Failed to send ICE candidate to participant", "participant", participant.ID, "room", room.Name, "err", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
iceHolder := make([]webrtc.ICECandidateInit, 0)
|
|
||||||
|
|
||||||
// ICE callback
|
|
||||||
participant.WebSocket.RegisterMessageCallback("ice", func(data []byte) {
|
|
||||||
var iceMsg connections.MessageICECandidate
|
|
||||||
if err = json.Unmarshal(data, &iceMsg); err != nil {
|
|
||||||
slog.Error("Failed to decode ICE candidate message from participant", "participant", participant.ID, "room", room.Name, "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if participant.PeerConnection.RemoteDescription() != nil {
|
|
||||||
if err = participant.PeerConnection.AddICECandidate(iceMsg.Candidate); err != nil {
|
|
||||||
slog.Error("Failed to add ICE candidate for participant", "participant", participant.ID, "room", room.Name, "err", err)
|
|
||||||
}
|
|
||||||
// Add held ICE candidates
|
|
||||||
for _, heldCandidate := range iceHolder {
|
|
||||||
if err = participant.PeerConnection.AddICECandidate(heldCandidate); err != nil {
|
|
||||||
slog.Error("Failed to add held ICE candidate for participant", "participant", participant.ID, "room", room.Name, "err", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
iceHolder = nil
|
|
||||||
} else {
|
|
||||||
iceHolder = append(iceHolder, iceMsg.Candidate)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// SDP answer callback
|
|
||||||
participant.WebSocket.RegisterMessageCallback("sdp", func(data []byte) {
|
|
||||||
var sdpMsg connections.MessageSDP
|
|
||||||
if err = json.Unmarshal(data, &sdpMsg); err != nil {
|
|
||||||
slog.Error("Failed to decode SDP message from participant", "participant", participant.ID, "room", room.Name, "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
handleParticipantSDP(participant, sdpMsg)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Log callback
|
|
||||||
participant.WebSocket.RegisterMessageCallback("log", func(data []byte) {
|
|
||||||
var logMsg connections.MessageLog
|
|
||||||
if err = json.Unmarshal(data, &logMsg); err != nil {
|
|
||||||
slog.Error("Failed to decode log message from participant", "participant", participant.ID, "room", room.Name, "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// TODO: Handle log message sending to metrics server
|
|
||||||
})
|
|
||||||
|
|
||||||
// Metrics callback
|
|
||||||
participant.WebSocket.RegisterMessageCallback("metrics", func(data []byte) {
|
|
||||||
// Ignore for now
|
|
||||||
})
|
|
||||||
|
|
||||||
participant.WebSocket.RegisterOnClose(func() {
|
|
||||||
slog.Debug("WebSocket closed for participant", "participant", participant.ID, "room", room.Name)
|
|
||||||
// Remove from Room
|
|
||||||
room.removeParticipantByID(participant.ID)
|
|
||||||
})
|
|
||||||
|
|
||||||
slog.Info("Participant ready, sending OK answer", "participant", participant.ID, "room", room.Name)
|
|
||||||
if err := participant.WebSocket.SendAnswerMessageWS(connections.AnswerOK); err != nil {
|
|
||||||
slog.Error("Failed to send OK answer", "participant", participant.ID, "room", room.Name, "err", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If room is online, also send offer
|
|
||||||
if room.Online {
|
|
||||||
if err = room.signalParticipantWithTracks(participant); err != nil {
|
|
||||||
slog.Error("Failed to signal participant with tracks", "participant", participant.ID, "room", room.Name, "err", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
active, provider := relay.IsRoomActive(room.ID)
|
|
||||||
if active {
|
|
||||||
slog.Debug("Room active remotely, requesting stream", "room", room.Name, "provider", provider)
|
|
||||||
if _, err := relay.requestStream(context.Background(), room.Name, room.ID, provider); err != nil {
|
|
||||||
slog.Error("Failed to request stream", "room", room.Name, "err", err)
|
|
||||||
} else {
|
|
||||||
slog.Debug("Stream requested successfully", "room", room.Name, "provider", provider)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SDP answer handler for participants
|
|
||||||
func handleParticipantSDP(participant *Participant, answerMsg connections.MessageSDP) {
|
|
||||||
// Get SDP offer
|
|
||||||
sdpAnswer := answerMsg.SDP.SDP
|
|
||||||
|
|
||||||
// Set remote description
|
|
||||||
err := participant.PeerConnection.SetRemoteDescription(webrtc.SessionDescription{
|
|
||||||
Type: webrtc.SDPTypeAnswer,
|
|
||||||
SDP: sdpAnswer,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to set remote SDP answer for participant", "participant", participant.ID, "err", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ForwardParticipantDataChannelMessage(participant *Participant, room *Room, data []byte) {
|
|
||||||
// Debug mode: Add latency timestamp
|
|
||||||
if common.GetFlags().Debug {
|
|
||||||
var inputMsg gen.ProtoMessageInput
|
|
||||||
if err := proto.Unmarshal(data, &inputMsg); err != nil {
|
|
||||||
slog.Error("Failed to decode input message from participant", "participant", participant.ID, "room", room.Name, "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
protoLat := inputMsg.GetMessageBase().GetLatency()
|
|
||||||
if protoLat != nil {
|
|
||||||
lat := common.LatencyTrackerFromProto(protoLat)
|
|
||||||
lat.AddTimestamp("relay_to_node")
|
|
||||||
protoLat = lat.ToProto()
|
|
||||||
}
|
|
||||||
if newData, err := proto.Marshal(&inputMsg); err != nil {
|
|
||||||
slog.Error("Failed to marshal input message from participant", "participant", participant.ID, "room", room.Name, "err", err)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
// Update data with the modified message
|
|
||||||
data = newData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forward to local room DataChannel if it exists (e.g., local ingest)
|
|
||||||
if room.DataChannel != nil {
|
|
||||||
if err := room.DataChannel.SendBinary(data); err != nil {
|
|
||||||
slog.Error("Failed to send input message to room", "participant", participant.ID, "room", room.Name, "err", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
"github.com/libp2p/go-reuseport"
|
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
|
||||||
"relay/internal/common"
|
|
||||||
"relay/internal/connections"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
var httpMux *http.ServeMux
|
|
||||||
|
|
||||||
func InitHTTPEndpoint(_ context.Context, ctxCancel context.CancelFunc) error {
|
|
||||||
// Create HTTP mux which serves our WS endpoint
|
|
||||||
httpMux = http.NewServeMux()
|
|
||||||
|
|
||||||
// Endpoints themselves
|
|
||||||
httpMux.Handle("/", http.NotFoundHandler())
|
|
||||||
// If control endpoint secret is set, enable the control endpoint
|
|
||||||
if len(common.GetFlags().ControlSecret) > 0 {
|
|
||||||
httpMux.HandleFunc("/api/control", corsAnyHandler(controlHandler))
|
|
||||||
}
|
|
||||||
// WS endpoint
|
|
||||||
httpMux.HandleFunc("/api/ws/{roomName}", corsAnyHandler(wsHandler))
|
|
||||||
|
|
||||||
// Get our serving port
|
|
||||||
port := common.GetFlags().EndpointPort
|
|
||||||
tlsCert := common.GetFlags().TLSCert
|
|
||||||
tlsKey := common.GetFlags().TLSKey
|
|
||||||
|
|
||||||
// Create re-usable listener port
|
|
||||||
httpListener, err := reuseport.Listen("tcp", ":"+strconv.Itoa(port))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create TCP listener: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log and start the endpoint server
|
|
||||||
if len(tlsCert) <= 0 && len(tlsKey) <= 0 {
|
|
||||||
slog.Info("Starting HTTP endpoint server", "port", port)
|
|
||||||
go func() {
|
|
||||||
if err := http.Serve(httpListener, httpMux); err != nil {
|
|
||||||
slog.Error("Failed to start HTTP server", "err", err)
|
|
||||||
ctxCancel()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
} else if len(tlsCert) > 0 && len(tlsKey) > 0 {
|
|
||||||
slog.Info("Starting HTTPS endpoint server", "port", port)
|
|
||||||
go func() {
|
|
||||||
if err := http.ServeTLS(httpListener, httpMux, tlsCert, tlsKey); err != nil {
|
|
||||||
slog.Error("Failed to start HTTPS server", "err", err)
|
|
||||||
ctxCancel()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
} else {
|
|
||||||
return errors.New("no TLS certificate or TLS key provided")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// logHTTPError logs (if verbose) and sends an error code to requester
|
|
||||||
func logHTTPError(w http.ResponseWriter, err string, code int) {
|
|
||||||
if common.GetFlags().Verbose {
|
|
||||||
slog.Error("HTTP error", "code", code, "message", err)
|
|
||||||
}
|
|
||||||
http.Error(w, err, code)
|
|
||||||
}
|
|
||||||
|
|
||||||
// corsAnyHandler allows any origin to access the endpoint
|
|
||||||
func corsAnyHandler(next func(w http.ResponseWriter, r *http.Request)) http.HandlerFunc {
|
|
||||||
return func(res http.ResponseWriter, req *http.Request) {
|
|
||||||
// Allow all origins
|
|
||||||
res.Header().Set("Access-Control-Allow-Origin", "*")
|
|
||||||
res.Header().Set("Access-Control-Allow-Methods", "*")
|
|
||||||
res.Header().Set("Access-Control-Allow-Headers", "*")
|
|
||||||
|
|
||||||
if req.Method != http.MethodOptions {
|
|
||||||
next(res, req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// wsHandler is the handler for the /api/ws/{roomName} endpoint
|
|
||||||
func wsHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Get given room name now
|
|
||||||
roomName := r.PathValue("roomName")
|
|
||||||
if len(roomName) <= 0 {
|
|
||||||
logHTTPError(w, "no room name given", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rel := GetRelay()
|
|
||||||
// Get or create room in any case
|
|
||||||
room := rel.GetOrCreateRoom(roomName)
|
|
||||||
|
|
||||||
// Upgrade to WebSocket
|
|
||||||
upgrader := websocket.Upgrader{
|
|
||||||
CheckOrigin: func(r *http.Request) bool {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
}
|
|
||||||
wsConn, err := upgrader.Upgrade(w, r, nil)
|
|
||||||
if err != nil {
|
|
||||||
logHTTPError(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create SafeWebSocket
|
|
||||||
ws := connections.NewSafeWebSocket(wsConn)
|
|
||||||
// Assign message handler for join request
|
|
||||||
ws.RegisterMessageCallback("join", func(data []byte) {
|
|
||||||
var joinMsg connections.MessageJoin
|
|
||||||
if err = json.Unmarshal(data, &joinMsg); err != nil {
|
|
||||||
slog.Error("Failed to unmarshal join message", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Debug("Join message", "room", room.Name, "joinerType", joinMsg.JoinerType)
|
|
||||||
|
|
||||||
// Handle join request, depending if it's from ingest/node or participant/client
|
|
||||||
switch joinMsg.JoinerType {
|
|
||||||
case connections.JoinerNode:
|
|
||||||
// If room already online, send InUse answer
|
|
||||||
if room.Online {
|
|
||||||
if err = ws.SendAnswerMessageWS(connections.AnswerInUse); err != nil {
|
|
||||||
slog.Error("Failed to send InUse answer to node", "room", room.Name, "err", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
room.AssignWebSocket(ws)
|
|
||||||
go IngestHandler(room)
|
|
||||||
case connections.JoinerClient:
|
|
||||||
// Create participant and add to room regardless of online status
|
|
||||||
participant := NewParticipant(ws)
|
|
||||||
room.AddParticipant(participant)
|
|
||||||
// If room not online, send Offline answer
|
|
||||||
if !room.Online {
|
|
||||||
if err = ws.SendAnswerMessageWS(connections.AnswerOffline); err != nil {
|
|
||||||
slog.Error("Failed to send offline answer to participant", "room", room.Name, "err", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
go ParticipantHandler(participant, room, rel)
|
|
||||||
default:
|
|
||||||
slog.Error("Unknown joiner type", "joinerType", joinMsg.JoinerType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unregister ourselves, if something happens on the other side they should just reconnect?
|
|
||||||
ws.UnregisterMessageCallback("join")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// controlMessage is the JSON struct for the control messages
|
|
||||||
type controlMessage struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Value string `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// controlHandler is the handler for the /api/control endpoint, for controlling this relay
|
|
||||||
func controlHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Check for control secret in Authorization header
|
|
||||||
authHeader := r.Header.Get("Authorization")
|
|
||||||
if len(authHeader) <= 0 || authHeader != common.GetFlags().ControlSecret {
|
|
||||||
logHTTPError(w, "missing or invalid Authorization header", http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle CORS preflight request
|
|
||||||
if r.Method == http.MethodOptions {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode the control message
|
|
||||||
var msg controlMessage
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&msg); err != nil {
|
|
||||||
logHTTPError(w, "failed to decode control message", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//relay := GetRelay()
|
|
||||||
switch msg.Type {
|
|
||||||
case "join_mesh":
|
|
||||||
// Join the mesh network, get relay address from msg.Value
|
|
||||||
if len(msg.Value) <= 0 {
|
|
||||||
logHTTPError(w, "missing relay address", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx := r.Context()
|
|
||||||
if err := GetRelay().ConnectToRelay(ctx, msg.Value); err != nil {
|
|
||||||
http.Error(w, fmt.Sprintf("Failed to connect: %v", err), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Write([]byte("Successfully connected to relay"))
|
|
||||||
default:
|
|
||||||
logHTTPError(w, "unknown control message type", http.StatusBadRequest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/pion/rtp"
|
|
||||||
"github.com/pion/webrtc/v4"
|
|
||||||
"io"
|
|
||||||
"log/slog"
|
|
||||||
"relay/internal/common"
|
|
||||||
"relay/internal/connections"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func IngestHandler(room *Room) {
|
|
||||||
relay := GetRelay()
|
|
||||||
|
|
||||||
// Callback for closing PeerConnection
|
|
||||||
onPCClose := func() {
|
|
||||||
slog.Debug("ingest PeerConnection closed", "room", room.Name)
|
|
||||||
room.Online = false
|
|
||||||
room.signalParticipantsOffline()
|
|
||||||
relay.DeleteRoomIfEmpty(room)
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
room.PeerConnection, err = common.CreatePeerConnection(onPCClose)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to create ingest PeerConnection", "room", room.Name, "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
room.PeerConnection.OnTrack(func(remoteTrack *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
|
|
||||||
localTrack, err := webrtc.NewTrackLocalStaticRTP(remoteTrack.Codec().RTPCodecCapability, remoteTrack.Kind().String(), fmt.Sprintf("nestri-%s-%s", room.Name, remoteTrack.Kind().String()))
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to create local track for room", "room", room.Name, "kind", remoteTrack.Kind(), "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
slog.Debug("Received track for room", "room", room.Name, "kind", remoteTrack.Kind())
|
|
||||||
|
|
||||||
// Set track and let Room handle state
|
|
||||||
room.SetTrack(remoteTrack.Kind(), localTrack)
|
|
||||||
|
|
||||||
// Prepare PlayoutDelayExtension so we don't need to recreate it for each packet
|
|
||||||
playoutExt := &rtp.PlayoutDelayExtension{
|
|
||||||
MinDelay: 0,
|
|
||||||
MaxDelay: 0,
|
|
||||||
}
|
|
||||||
playoutPayload, err := playoutExt.Marshal()
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to marshal PlayoutDelayExtension for room", "room", room.Name, "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
rtpPacket, _, err := remoteTrack.ReadRTP()
|
|
||||||
if err != nil {
|
|
||||||
if !errors.Is(err, io.EOF) {
|
|
||||||
slog.Error("Failed to read RTP from remote track for room", "room", room.Name, "err", err)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use PlayoutDelayExtension for low latency, only for video tracks
|
|
||||||
if err := rtpPacket.SetExtension(common.ExtensionMap[common.ExtensionPlayoutDelay], playoutPayload); err != nil {
|
|
||||||
slog.Error("Failed to set PlayoutDelayExtension for room", "room", room.Name, "err", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err = localTrack.WriteRTP(rtpPacket)
|
|
||||||
if err != nil && !errors.Is(err, io.ErrClosedPipe) {
|
|
||||||
slog.Error("Failed to write RTP to local track for room", "room", room.Name, "err", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Debug("Track closed for room", "room", room.Name, "kind", remoteTrack.Kind())
|
|
||||||
|
|
||||||
// Clear track when done
|
|
||||||
room.SetTrack(remoteTrack.Kind(), nil)
|
|
||||||
})
|
|
||||||
|
|
||||||
room.PeerConnection.OnDataChannel(func(dc *webrtc.DataChannel) {
|
|
||||||
room.DataChannel = connections.NewNestriDataChannel(dc)
|
|
||||||
slog.Debug("Ingest received DataChannel for room", "room", room.Name)
|
|
||||||
|
|
||||||
room.DataChannel.RegisterOnOpen(func() {
|
|
||||||
slog.Debug("ingest DataChannel opened for room", "room", room.Name)
|
|
||||||
})
|
|
||||||
|
|
||||||
room.DataChannel.OnClose(func() {
|
|
||||||
slog.Debug("ingest DataChannel closed for room", "room", room.Name)
|
|
||||||
})
|
|
||||||
|
|
||||||
// We do not handle any messages from ingest via DataChannel yet
|
|
||||||
})
|
|
||||||
|
|
||||||
room.PeerConnection.OnICECandidate(func(candidate *webrtc.ICECandidate) {
|
|
||||||
if candidate == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
slog.Debug("ingest received ICECandidate for room", "room", room.Name)
|
|
||||||
err = room.WebSocket.SendICECandidateMessageWS(candidate.ToJSON())
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to send ICE candidate message to ingest for room", "room", room.Name, "err", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
iceHolder := make([]webrtc.ICECandidateInit, 0)
|
|
||||||
|
|
||||||
// ICE callback
|
|
||||||
room.WebSocket.RegisterMessageCallback("ice", func(data []byte) {
|
|
||||||
var iceMsg connections.MessageICECandidate
|
|
||||||
if err = json.Unmarshal(data, &iceMsg); err != nil {
|
|
||||||
slog.Error("Failed to decode ICE candidate message from ingest for room", "room", room.Name, "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if room.PeerConnection != nil {
|
|
||||||
if room.PeerConnection.RemoteDescription() != nil {
|
|
||||||
if err = room.PeerConnection.AddICECandidate(iceMsg.Candidate); err != nil {
|
|
||||||
slog.Error("Failed to add ICE candidate for room", "room", room.Name, "err", err)
|
|
||||||
}
|
|
||||||
for _, heldCandidate := range iceHolder {
|
|
||||||
if err = room.PeerConnection.AddICECandidate(heldCandidate); err != nil {
|
|
||||||
slog.Error("Failed to add held ICE candidate for room", "room", room.Name, "err", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
iceHolder = make([]webrtc.ICECandidateInit, 0)
|
|
||||||
} else {
|
|
||||||
iceHolder = append(iceHolder, iceMsg.Candidate)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
slog.Error("ICE candidate received but PeerConnection is nil for room", "room", room.Name)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// SDP offer callback
|
|
||||||
room.WebSocket.RegisterMessageCallback("sdp", func(data []byte) {
|
|
||||||
var sdpMsg connections.MessageSDP
|
|
||||||
if err = json.Unmarshal(data, &sdpMsg); err != nil {
|
|
||||||
slog.Error("Failed to decode SDP message from ingest for room", "room", room.Name, "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
answer := handleIngestSDP(room, sdpMsg)
|
|
||||||
if answer != nil {
|
|
||||||
if err = room.WebSocket.SendSDPMessageWS(*answer); err != nil {
|
|
||||||
slog.Error("Failed to send SDP answer message to ingest for room", "room", room.Name, "err", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
slog.Error("Failed to handle ingest SDP message for room", "room", room.Name)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Log callback
|
|
||||||
room.WebSocket.RegisterMessageCallback("log", func(data []byte) {
|
|
||||||
var logMsg connections.MessageLog
|
|
||||||
if err = json.Unmarshal(data, &logMsg); err != nil {
|
|
||||||
slog.Error("Failed to decode log message from ingest for room", "room", room.Name, "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// TODO: Handle log message sending to metrics server
|
|
||||||
})
|
|
||||||
|
|
||||||
// Metrics callback
|
|
||||||
room.WebSocket.RegisterMessageCallback("metrics", func(data []byte) {
|
|
||||||
var metricsMsg connections.MessageMetrics
|
|
||||||
if err = json.Unmarshal(data, &metricsMsg); err != nil {
|
|
||||||
slog.Error("Failed to decode metrics message from ingest for room", "room", room.Name, "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// TODO: Handle metrics message sending to metrics server
|
|
||||||
})
|
|
||||||
|
|
||||||
room.WebSocket.RegisterOnClose(func() {
|
|
||||||
slog.Debug("ingest WebSocket closed for room", "room", room.Name)
|
|
||||||
room.Online = false
|
|
||||||
room.signalParticipantsOffline()
|
|
||||||
relay.DeleteRoomIfEmpty(room)
|
|
||||||
})
|
|
||||||
|
|
||||||
slog.Info("Room is ready, sending OK answer to ingest", "room", room.Name)
|
|
||||||
if err = room.WebSocket.SendAnswerMessageWS(connections.AnswerOK); err != nil {
|
|
||||||
slog.Error("Failed to send OK answer message to ingest for room", "room", room.Name, "err", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SDP offer handler, returns SDP answer
|
|
||||||
func handleIngestSDP(room *Room, offerMsg connections.MessageSDP) *webrtc.SessionDescription {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
sdpOffer := offerMsg.SDP.SDP
|
|
||||||
sdpOffer = strings.Replace(sdpOffer, ";sprop-maxcapturerate=24000", "", -1)
|
|
||||||
|
|
||||||
err = room.PeerConnection.SetRemoteDescription(webrtc.SessionDescription{
|
|
||||||
Type: webrtc.SDPTypeOffer,
|
|
||||||
SDP: sdpOffer,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to set remote description for room", "room", room.Name, "err", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
answer, err := room.PeerConnection.CreateAnswer(nil)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to create SDP answer for room", "room", room.Name, "err", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = room.PeerConnection.SetLocalDescription(answer)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to set local description for room", "room", room.Name, "err", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &answer
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/oklog/ulid/v2"
|
|
||||||
"github.com/pion/webrtc/v4"
|
|
||||||
"log/slog"
|
|
||||||
"math/rand"
|
|
||||||
"relay/internal/common"
|
|
||||||
"relay/internal/connections"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Participant struct {
|
|
||||||
ID ulid.ULID //< Internal IDs are useful to keeping unique internal track and not have conflicts later
|
|
||||||
Name string
|
|
||||||
WebSocket *connections.SafeWebSocket
|
|
||||||
PeerConnection *webrtc.PeerConnection
|
|
||||||
DataChannel *connections.NestriDataChannel
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewParticipant(ws *connections.SafeWebSocket) *Participant {
|
|
||||||
id, err := common.NewULID()
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to create ULID for Participant", "err", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &Participant{
|
|
||||||
ID: id,
|
|
||||||
Name: createRandomName(),
|
|
||||||
WebSocket: ws,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Participant) addTrack(trackLocal *webrtc.TrackLocalStaticRTP) error {
|
|
||||||
rtpSender, err := p.PeerConnection.AddTrack(trackLocal)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
rtcpBuffer := make([]byte, 1400)
|
|
||||||
for {
|
|
||||||
if _, _, rtcpErr := rtpSender.Read(rtcpBuffer); rtcpErr != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Participant) signalOffer() error {
|
|
||||||
if p.PeerConnection == nil {
|
|
||||||
return fmt.Errorf("peer connection is nil for participant: '%s' - cannot signal offer", p.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
offer, err := p.PeerConnection.CreateOffer(nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = p.PeerConnection.SetLocalDescription(offer)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.WebSocket.SendSDPMessageWS(offer)
|
|
||||||
}
|
|
||||||
|
|
||||||
var namesFirst = []string{"Happy", "Sad", "Angry", "Calm", "Excited", "Bored", "Confused", "Confident", "Curious", "Depressed", "Disappointed", "Embarrassed", "Energetic", "Fearful", "Frustrated", "Glad", "Guilty", "Hopeful", "Impatient", "Jealous", "Lonely", "Motivated", "Nervous", "Optimistic", "Pessimistic", "Proud", "Relaxed", "Shy", "Stressed", "Surprised", "Tired", "Worried"}
|
|
||||||
var namesSecond = []string{"Dragon", "Unicorn", "Troll", "Goblin", "Elf", "Dwarf", "Ogre", "Gnome", "Mermaid", "Siren", "Vampire", "Ghoul", "Werewolf", "Minotaur", "Centaur", "Griffin", "Phoenix", "Wyvern", "Hydra", "Kraken"}
|
|
||||||
|
|
||||||
func createRandomName() string {
|
|
||||||
randomFirst := namesFirst[rand.Intn(len(namesFirst))]
|
|
||||||
randomSecond := namesSecond[rand.Intn(len(namesSecond))]
|
|
||||||
return randomFirst + " " + randomSecond
|
|
||||||
}
|
|
||||||
@@ -1,702 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/libp2p/go-libp2p"
|
|
||||||
"github.com/libp2p/go-libp2p-pubsub"
|
|
||||||
"github.com/libp2p/go-libp2p/core/host"
|
|
||||||
"github.com/libp2p/go-libp2p/core/network"
|
|
||||||
"github.com/libp2p/go-libp2p/core/peer"
|
|
||||||
"github.com/libp2p/go-libp2p/core/pnet"
|
|
||||||
"github.com/libp2p/go-libp2p/p2p/security/noise"
|
|
||||||
"github.com/multiformats/go-multiaddr"
|
|
||||||
"github.com/oklog/ulid/v2"
|
|
||||||
"github.com/pion/webrtc/v4"
|
|
||||||
"io"
|
|
||||||
"log/slog"
|
|
||||||
"relay/internal/common"
|
|
||||||
"relay/internal/connections"
|
|
||||||
)
|
|
||||||
|
|
||||||
var globalRelay *Relay
|
|
||||||
|
|
||||||
// networkNotifier logs connection events
|
|
||||||
type networkNotifier struct{}
|
|
||||||
|
|
||||||
func (n *networkNotifier) Connected(net network.Network, conn network.Conn) {
|
|
||||||
slog.Info("Peer connected", "local", conn.LocalPeer(), "remote", conn.RemotePeer())
|
|
||||||
}
|
|
||||||
func (n *networkNotifier) Disconnected(net network.Network, conn network.Conn) {
|
|
||||||
slog.Info("Peer disconnected", "local", conn.LocalPeer(), "remote", conn.RemotePeer())
|
|
||||||
}
|
|
||||||
func (n *networkNotifier) Listen(net network.Network, addr multiaddr.Multiaddr) {}
|
|
||||||
func (n *networkNotifier) ListenClose(net network.Network, addr multiaddr.Multiaddr) {}
|
|
||||||
|
|
||||||
type ICEMessage struct {
|
|
||||||
PeerID string
|
|
||||||
TargetID string
|
|
||||||
RoomID ulid.ULID
|
|
||||||
Candidate []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type Relay struct {
|
|
||||||
ID peer.ID
|
|
||||||
Rooms *common.SafeMap[ulid.ULID, *Room]
|
|
||||||
Host host.Host // libp2p host for peer-to-peer networking
|
|
||||||
PubSub *pubsub.PubSub // PubSub for state synchronization
|
|
||||||
MeshState *common.SafeMap[ulid.ULID, RoomInfo] // room ID -> state
|
|
||||||
RelayPCs *common.SafeMap[ulid.ULID, *webrtc.PeerConnection] // room ID -> relay PeerConnection
|
|
||||||
pubTopicState *pubsub.Topic // topic for room states
|
|
||||||
pubTopicICECandidate *pubsub.Topic // topic for ICE candidates aimed to this relay
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRelay(ctx context.Context, port int) (*Relay, error) {
|
|
||||||
listenAddrs := []string{
|
|
||||||
fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", port), // IPv4
|
|
||||||
fmt.Sprintf("/ip6/::/tcp/%d", port), // IPv6
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use "testToken" as the pre-shared token for authentication
|
|
||||||
// TODO: Give via flags, before PR commit
|
|
||||||
token := "testToken"
|
|
||||||
// Generate 32-byte PSK from the token using SHA-256
|
|
||||||
shaToken := sha256.Sum256([]byte(token))
|
|
||||||
tokenPSK := pnet.PSK(shaToken[:])
|
|
||||||
|
|
||||||
// Initialize libp2p host
|
|
||||||
p2pHost, err := libp2p.New(
|
|
||||||
libp2p.ListenAddrStrings(listenAddrs...),
|
|
||||||
libp2p.Security(noise.ID, noise.New),
|
|
||||||
libp2p.EnableRelay(),
|
|
||||||
libp2p.EnableHolePunching(),
|
|
||||||
libp2p.PrivateNetwork(tokenPSK),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create libp2p host for relay: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up pubsub
|
|
||||||
p2pPubsub, err := pubsub.NewGossipSub(ctx, p2pHost)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create pubsub: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add network notifier to log connections
|
|
||||||
p2pHost.Network().Notify(&networkNotifier{})
|
|
||||||
|
|
||||||
r := &Relay{
|
|
||||||
ID: p2pHost.ID(),
|
|
||||||
Host: p2pHost,
|
|
||||||
PubSub: p2pPubsub,
|
|
||||||
Rooms: common.NewSafeMap[ulid.ULID, *Room](),
|
|
||||||
MeshState: common.NewSafeMap[ulid.ULID, RoomInfo](),
|
|
||||||
RelayPCs: common.NewSafeMap[ulid.ULID, *webrtc.PeerConnection](),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up state synchronization and stream handling
|
|
||||||
r.setupStateSync(ctx)
|
|
||||||
r.setupStreamHandler()
|
|
||||||
|
|
||||||
slog.Info("Relay initialized", "id", r.ID, "addrs", p2pHost.Addrs())
|
|
||||||
|
|
||||||
peerInfo := peer.AddrInfo{
|
|
||||||
ID: p2pHost.ID(),
|
|
||||||
Addrs: p2pHost.Addrs(),
|
|
||||||
}
|
|
||||||
addrs, err := peer.AddrInfoToP2pAddrs(&peerInfo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to convert peer info to addresses: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Debug("Connect with one of the following URLs below:")
|
|
||||||
for _, addr := range addrs {
|
|
||||||
slog.Debug(fmt.Sprintf("- %s", addr.String()))
|
|
||||||
}
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func InitRelay(ctx context.Context, ctxCancel context.CancelFunc, port int) error {
|
|
||||||
var err error
|
|
||||||
globalRelay, err = NewRelay(ctx, port)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create relay: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := common.InitWebRTCAPI(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := InitHTTPEndpoint(ctx, ctxCancel); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("Relay initialized", "id", globalRelay.ID)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetRelay() *Relay {
|
|
||||||
return globalRelay
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Relay) GetRoomByID(id ulid.ULID) *Room {
|
|
||||||
if room, ok := r.Rooms.Get(id); ok {
|
|
||||||
return room
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Relay) GetOrCreateRoom(name string) *Room {
|
|
||||||
if room := r.GetRoomByName(name); room != nil {
|
|
||||||
return room
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := common.NewULID()
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to generate new ULID for room", "err", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
room := NewRoom(name, id, r.ID)
|
|
||||||
room.Relay = r
|
|
||||||
r.Rooms.Set(room.ID, room)
|
|
||||||
|
|
||||||
slog.Debug("Created new room", "name", name, "id", room.ID)
|
|
||||||
return room
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Relay) DeleteRoomIfEmpty(room *Room) {
|
|
||||||
participantCount := room.Participants.Len()
|
|
||||||
if participantCount > 0 {
|
|
||||||
slog.Debug("Room not empty, not deleting", "name", room.Name, "id", room.ID, "participants", participantCount)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a "tombstone" state for the room, this allows propagation of the room deletion
|
|
||||||
tombstoneState := RoomInfo{
|
|
||||||
ID: room.ID,
|
|
||||||
Name: room.Name,
|
|
||||||
Online: false,
|
|
||||||
OwnerID: room.OwnerID,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Publish updated state to mesh
|
|
||||||
if err := r.publishRoomState(context.Background(), tombstoneState); err != nil {
|
|
||||||
slog.Error("Failed to publish room states on change", "room", room.Name, "err", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("Deleting room since empty and offline", "name", room.Name, "id", room.ID)
|
|
||||||
r.Rooms.Delete(room.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Relay) setupStateSync(ctx context.Context) {
|
|
||||||
var err error
|
|
||||||
r.pubTopicState, err = r.PubSub.Join("room-states")
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to join pubsub topic", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sub, err := r.pubTopicState.Subscribe()
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to subscribe to topic", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
r.pubTopicICECandidate, err = r.PubSub.Join("ice-candidates")
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to join ICE candidates topic", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
iceCandidateSub, err := r.pubTopicICECandidate.Subscribe()
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to subscribe to ICE candidates topic", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle state updates only from authenticated peers
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
msg, err := sub.Next(ctx)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Error receiving pubsub message", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if msg.GetFrom() == r.Host.ID() {
|
|
||||||
continue // Ignore own messages
|
|
||||||
}
|
|
||||||
var states []RoomInfo
|
|
||||||
if err := json.Unmarshal(msg.Data, &states); err != nil {
|
|
||||||
slog.Error("Failed to unmarshal room states", "err", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
r.updateMeshState(states)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Handle incoming ICE candidates for given room
|
|
||||||
go func() {
|
|
||||||
// Map of ICE candidate slices per room ID
|
|
||||||
iceHolder := make(map[ulid.ULID][]webrtc.ICECandidateInit)
|
|
||||||
|
|
||||||
for {
|
|
||||||
msg, err := iceCandidateSub.Next(ctx)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Error receiving ICE candidate message", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if msg.GetFrom() == r.Host.ID() {
|
|
||||||
continue // Ignore own messages
|
|
||||||
}
|
|
||||||
|
|
||||||
var iceMsg ICEMessage
|
|
||||||
if err := json.Unmarshal(msg.Data, &iceMsg); err != nil {
|
|
||||||
slog.Error("Failed to unmarshal ICE candidate message", "err", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if iceMsg.TargetID != r.ID.String() {
|
|
||||||
continue // Ignore messages not meant for this relay
|
|
||||||
}
|
|
||||||
|
|
||||||
if iceHolder[iceMsg.RoomID] == nil {
|
|
||||||
iceHolder[iceMsg.RoomID] = make([]webrtc.ICECandidateInit, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if pc, ok := r.RelayPCs.Get(iceMsg.RoomID); ok {
|
|
||||||
// Unmarshal ice candidate
|
|
||||||
var candidate webrtc.ICECandidateInit
|
|
||||||
if err := json.Unmarshal(iceMsg.Candidate, &candidate); err != nil {
|
|
||||||
slog.Error("Failed to unmarshal ICE candidate", "err", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if pc.RemoteDescription() != nil {
|
|
||||||
if err := pc.AddICECandidate(candidate); err != nil {
|
|
||||||
slog.Error("Failed to add ICE candidate", "err", err)
|
|
||||||
}
|
|
||||||
// Add any held candidates
|
|
||||||
for _, heldCandidate := range iceHolder[iceMsg.RoomID] {
|
|
||||||
if err := pc.AddICECandidate(heldCandidate); err != nil {
|
|
||||||
slog.Error("Failed to add held ICE candidate", "err", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
iceHolder[iceMsg.RoomID] = make([]webrtc.ICECandidateInit, 0)
|
|
||||||
} else {
|
|
||||||
iceHolder[iceMsg.RoomID] = append(iceHolder[iceMsg.RoomID], candidate)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
slog.Error("PeerConnection for room not found when adding ICE candidate", "roomID", iceMsg.RoomID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Relay) publishRoomState(ctx context.Context, state RoomInfo) error {
|
|
||||||
data, err := json.Marshal([]RoomInfo{state})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return r.pubTopicState.Publish(ctx, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Relay) publishRoomStates(ctx context.Context) error {
|
|
||||||
var states []RoomInfo
|
|
||||||
for _, room := range r.Rooms.Copy() {
|
|
||||||
states = append(states, RoomInfo{
|
|
||||||
ID: room.ID,
|
|
||||||
Name: room.Name,
|
|
||||||
Online: room.Online,
|
|
||||||
OwnerID: r.ID,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
data, err := json.Marshal(states)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return r.pubTopicState.Publish(ctx, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Relay) updateMeshState(states []RoomInfo) {
|
|
||||||
for _, state := range states {
|
|
||||||
if state.OwnerID == r.ID {
|
|
||||||
continue // Skip own state
|
|
||||||
}
|
|
||||||
existing, exists := r.MeshState.Get(state.ID)
|
|
||||||
r.MeshState.Set(state.ID, state)
|
|
||||||
slog.Debug("Updated mesh state", "room", state.Name, "online", state.Online, "owner", state.OwnerID)
|
|
||||||
|
|
||||||
// React to state changes
|
|
||||||
if !exists || existing.Online != state.Online {
|
|
||||||
room := r.GetRoomByName(state.Name)
|
|
||||||
if state.Online {
|
|
||||||
if room == nil || !room.Online {
|
|
||||||
slog.Info("Room became active remotely, requesting stream", "room", state.Name, "owner", state.OwnerID)
|
|
||||||
go func() {
|
|
||||||
if _, err := r.requestStream(context.Background(), state.Name, state.ID, state.OwnerID); err != nil {
|
|
||||||
slog.Error("Failed to request stream", "room", state.Name, "err", err)
|
|
||||||
} else {
|
|
||||||
slog.Info("Successfully requested stream", "room", state.Name, "owner", state.OwnerID)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
} else if room != nil && room.Online {
|
|
||||||
slog.Info("Room became inactive remotely, stopping local stream", "room", state.Name)
|
|
||||||
if pc, ok := r.RelayPCs.Get(state.ID); ok {
|
|
||||||
_ = pc.Close()
|
|
||||||
r.RelayPCs.Delete(state.ID)
|
|
||||||
}
|
|
||||||
room.Online = false
|
|
||||||
room.signalParticipantsOffline()
|
|
||||||
} else if room == nil && !exists {
|
|
||||||
slog.Info("Received tombstone state for room", "name", state.Name, "id", state.ID)
|
|
||||||
if pc, ok := r.RelayPCs.Get(state.ID); ok {
|
|
||||||
_ = pc.Close()
|
|
||||||
r.RelayPCs.Delete(state.ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Relay) IsRoomActive(roomID ulid.ULID) (bool, peer.ID) {
|
|
||||||
if state, exists := r.MeshState.Get(roomID); exists && state.Online {
|
|
||||||
return true, state.OwnerID
|
|
||||||
}
|
|
||||||
return false, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Relay) GetRoomByName(name string) *Room {
|
|
||||||
for _, room := range r.Rooms.Copy() {
|
|
||||||
if room.Name == name {
|
|
||||||
return room
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeMessage(stream network.Stream, data []byte) error {
|
|
||||||
length := uint32(len(data))
|
|
||||||
if err := binary.Write(stream, binary.BigEndian, length); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err := stream.Write(data)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func readMessage(stream network.Stream) ([]byte, error) {
|
|
||||||
var length uint32
|
|
||||||
if err := binary.Read(stream, binary.BigEndian, &length); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data := make([]byte, length)
|
|
||||||
_, err := io.ReadFull(stream, data)
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Relay) setupStreamHandler() {
|
|
||||||
r.Host.SetStreamHandler("/nestri-relay/stream/1.0.0", func(stream network.Stream) {
|
|
||||||
defer func(stream network.Stream) {
|
|
||||||
err := stream.Close()
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to close stream", "err", err)
|
|
||||||
}
|
|
||||||
}(stream)
|
|
||||||
remotePeer := stream.Conn().RemotePeer()
|
|
||||||
|
|
||||||
roomNameData, err := readMessage(stream)
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
slog.Error("Failed to read room name", "peer", remotePeer, "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
roomName := string(roomNameData)
|
|
||||||
|
|
||||||
slog.Info("Stream request from peer", "peer", remotePeer, "room", roomName)
|
|
||||||
|
|
||||||
room := r.GetRoomByName(roomName)
|
|
||||||
if room == nil || !room.Online {
|
|
||||||
slog.Error("Cannot provide stream for inactive room", "room", roomName)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pc, err := common.CreatePeerConnection(func() {
|
|
||||||
r.RelayPCs.Delete(room.ID)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to create relay PeerConnection", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
r.RelayPCs.Set(room.ID, pc)
|
|
||||||
|
|
||||||
if room.AudioTrack != nil {
|
|
||||||
_, err := pc.AddTrack(room.AudioTrack)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to add audio track", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if room.VideoTrack != nil {
|
|
||||||
_, err := pc.AddTrack(room.VideoTrack)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to add video track", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
settingOrdered := true
|
|
||||||
settingMaxRetransmits := uint16(0)
|
|
||||||
dc, err := pc.CreateDataChannel("relay-data", &webrtc.DataChannelInit{
|
|
||||||
Ordered: &settingOrdered,
|
|
||||||
MaxRetransmits: &settingMaxRetransmits,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to create relay DataChannel", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
relayDC := connections.NewNestriDataChannel(dc)
|
|
||||||
|
|
||||||
relayDC.RegisterOnOpen(func() {
|
|
||||||
slog.Debug("Relay DataChannel opened", "room", roomName)
|
|
||||||
})
|
|
||||||
|
|
||||||
relayDC.RegisterOnClose(func() {
|
|
||||||
slog.Debug("Relay DataChannel closed", "room", roomName)
|
|
||||||
})
|
|
||||||
|
|
||||||
relayDC.RegisterMessageCallback("input", func(data []byte) {
|
|
||||||
if room.DataChannel != nil {
|
|
||||||
// Forward message to the room's data channel
|
|
||||||
if err := room.DataChannel.SendBinary(data); err != nil {
|
|
||||||
slog.Error("Failed to send DataChannel message", "room", roomName, "err", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
offer, err := pc.CreateOffer(nil)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to create offer", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := pc.SetLocalDescription(offer); err != nil {
|
|
||||||
slog.Error("Failed to set local description", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
offerData, err := json.Marshal(offer)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to marshal offer", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := writeMessage(stream, offerData); err != nil {
|
|
||||||
slog.Error("Failed to send offer", "peer", remotePeer, "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle our generated ICE candidates
|
|
||||||
pc.OnICECandidate(func(candidate *webrtc.ICECandidate) {
|
|
||||||
if candidate == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
candidateData, err := json.Marshal(candidate.ToJSON())
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to marshal ICE candidate", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
iceMsg := ICEMessage{
|
|
||||||
PeerID: r.Host.ID().String(),
|
|
||||||
TargetID: remotePeer.String(),
|
|
||||||
RoomID: room.ID,
|
|
||||||
Candidate: candidateData,
|
|
||||||
}
|
|
||||||
data, err := json.Marshal(iceMsg)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to marshal ICE message", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := r.pubTopicICECandidate.Publish(context.Background(), data); err != nil {
|
|
||||||
slog.Error("Failed to publish ICE candidate message", "err", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
answerData, err := readMessage(stream)
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
slog.Error("Failed to read answer", "peer", remotePeer, "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var answer webrtc.SessionDescription
|
|
||||||
if err := json.Unmarshal(answerData, &answer); err != nil {
|
|
||||||
slog.Error("Failed to unmarshal answer", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := pc.SetRemoteDescription(answer); err != nil {
|
|
||||||
slog.Error("Failed to set remote description", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Relay) requestStream(ctx context.Context, roomName string, roomID ulid.ULID, providerPeer peer.ID) (*webrtc.PeerConnection, error) {
|
|
||||||
stream, err := r.Host.NewStream(ctx, providerPeer, "/nestri-relay/stream/1.0.0")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create stream: %w", err)
|
|
||||||
}
|
|
||||||
defer func(stream network.Stream) {
|
|
||||||
err := stream.Close()
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to close stream", "err", err)
|
|
||||||
}
|
|
||||||
}(stream)
|
|
||||||
|
|
||||||
if err := writeMessage(stream, []byte(roomName)); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to send room name: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
room := r.GetRoomByName(roomName)
|
|
||||||
if room == nil {
|
|
||||||
room = NewRoom(roomName, roomID, providerPeer)
|
|
||||||
r.Rooms.Set(roomID, room)
|
|
||||||
} else if room.ID != roomID {
|
|
||||||
// Mismatch, prefer the one from the provider
|
|
||||||
// TODO: When mesh is created, if there are mismatches, we should have relays negotiate common room IDs
|
|
||||||
room.ID = roomID
|
|
||||||
room.OwnerID = providerPeer
|
|
||||||
r.Rooms.Set(roomID, room)
|
|
||||||
}
|
|
||||||
|
|
||||||
pc, err := common.CreatePeerConnection(func() {
|
|
||||||
r.RelayPCs.Delete(roomID)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create PeerConnection: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.RelayPCs.Set(roomID, pc)
|
|
||||||
|
|
||||||
offerData, err := readMessage(stream)
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
return nil, fmt.Errorf("failed to read offer: %w", err)
|
|
||||||
}
|
|
||||||
var offer webrtc.SessionDescription
|
|
||||||
if err := json.Unmarshal(offerData, &offer); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to unmarshal offer: %w", err)
|
|
||||||
}
|
|
||||||
if err := pc.SetRemoteDescription(offer); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to set remote description: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pc.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
|
|
||||||
localTrack, _ := webrtc.NewTrackLocalStaticRTP(track.Codec().RTPCodecCapability, track.ID(), "relay-"+roomName+"-"+track.Kind().String())
|
|
||||||
slog.Debug("Received track for mesh relay room", "room", roomName, "kind", track.Kind())
|
|
||||||
|
|
||||||
room.SetTrack(track.Kind(), localTrack)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
rtpPacket, _, err := track.ReadRTP()
|
|
||||||
if err != nil {
|
|
||||||
if !errors.Is(err, io.EOF) {
|
|
||||||
slog.Error("Failed to read RTP packet from remote track for room", "room", roomName, "err", err)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
err = localTrack.WriteRTP(rtpPacket)
|
|
||||||
if err != nil && !errors.Is(err, io.ErrClosedPipe) {
|
|
||||||
slog.Error("Failed to write RTP to local track for room", "room", room.Name, "err", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
})
|
|
||||||
|
|
||||||
// ICE candidate handling
|
|
||||||
pc.OnICECandidate(func(candidate *webrtc.ICECandidate) {
|
|
||||||
if candidate == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
candidateData, err := json.Marshal(candidate.ToJSON())
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to marshal ICE candidate", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
iceMsg := ICEMessage{
|
|
||||||
PeerID: r.Host.ID().String(),
|
|
||||||
TargetID: providerPeer.String(),
|
|
||||||
RoomID: roomID,
|
|
||||||
Candidate: candidateData,
|
|
||||||
}
|
|
||||||
data, err := json.Marshal(iceMsg)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to marshal ICE message", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := r.pubTopicICECandidate.Publish(ctx, data); err != nil {
|
|
||||||
slog.Error("Failed to publish ICE candidate message", "err", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
pc.OnDataChannel(func(dc *webrtc.DataChannel) {
|
|
||||||
relayDC := connections.NewNestriDataChannel(dc)
|
|
||||||
slog.Debug("Received DataChannel from peer", "room", roomName)
|
|
||||||
|
|
||||||
relayDC.RegisterOnOpen(func() {
|
|
||||||
slog.Debug("Relay DataChannel opened", "room", roomName)
|
|
||||||
})
|
|
||||||
|
|
||||||
relayDC.OnClose(func() {
|
|
||||||
slog.Debug("Relay DataChannel closed", "room", roomName)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Override room DataChannel with the mesh-relay one to forward messages
|
|
||||||
room.DataChannel = relayDC
|
|
||||||
})
|
|
||||||
|
|
||||||
answer, err := pc.CreateAnswer(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create answer: %w", err)
|
|
||||||
}
|
|
||||||
if err := pc.SetLocalDescription(answer); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to set local description: %w", err)
|
|
||||||
}
|
|
||||||
answerData, err := json.Marshal(answer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to marshal answer: %w", err)
|
|
||||||
}
|
|
||||||
if err := writeMessage(stream, answerData); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to send answer: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectToRelay manually connects to another relay by its multiaddress
|
|
||||||
func (r *Relay) ConnectToRelay(ctx context.Context, addr string) error {
|
|
||||||
// Parse the multiaddress
|
|
||||||
ma, err := multiaddr.NewMultiaddr(addr)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Invalid multiaddress", "addr", addr, "err", err)
|
|
||||||
return fmt.Errorf("invalid multiaddress: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract peer ID from multiaddress
|
|
||||||
peerInfo, err := peer.AddrInfoFromP2pAddr(ma)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to extract peer info", "addr", addr, "err", err)
|
|
||||||
return fmt.Errorf("failed to extract peer info: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect to the peer
|
|
||||||
if err := r.Host.Connect(ctx, *peerInfo); err != nil {
|
|
||||||
slog.Error("Failed to connect to peer", "peer", peerInfo.ID, "addr", addr, "err", err)
|
|
||||||
return fmt.Errorf("failed to connect: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Publish challenge on join
|
|
||||||
//go r.sendAuthChallenge(ctx)
|
|
||||||
|
|
||||||
slog.Info("Successfully connected to peer", "peer", peerInfo.ID, "addr", addr)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user