Create "working" example

This commit is contained in:
AlexanderHD27
2024-11-03 23:52:06 +01:00
parent 41a32b450c
commit f9ce4db95a
42 changed files with 2067 additions and 21 deletions

3
gobot-gui/api/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"deno.enable": true
}

0
gobot-gui/api/asd.json Normal file
View File

View File

@@ -1,5 +1,8 @@
{
"tasks": {
"dev": "deno run --watch main.ts"
},
"imports": {
"@earthstar/dns-sd": "jsr:@earthstar/dns-sd@^3.1.0"
}
}

169
gobot-gui/api/deno.lock generated Normal file
View File

@@ -0,0 +1,169 @@
{
"version": "3",
"packages": {
"specifiers": {
"jsr:@earthstar/dns-sd": "jsr:@earthstar/dns-sd@3.1.0",
"jsr:@oak/commons@0.7": "jsr:@oak/commons@0.7.0",
"jsr:@oak/oak@14": "jsr:@oak/oak@14.2.0",
"jsr:@std/assert": "jsr:@std/assert@0.218.2",
"jsr:@std/assert@0.218": "jsr:@std/assert@0.218.2",
"jsr:@std/assert@^0.218.2": "jsr:@std/assert@0.218.2",
"jsr:@std/bytes@0.218": "jsr:@std/bytes@0.218.2",
"jsr:@std/bytes@^0.218.2": "jsr:@std/bytes@0.218.2",
"jsr:@std/bytes@^0.224.0": "jsr:@std/bytes@0.224.0",
"jsr:@std/crypto": "jsr:@std/crypto@0.218.2",
"jsr:@std/crypto@0.218": "jsr:@std/crypto@0.218.2",
"jsr:@std/encoding": "jsr:@std/encoding@0.218.2",
"jsr:@std/encoding@0.218": "jsr:@std/encoding@0.218.2",
"jsr:@std/encoding@^0.218.2": "jsr:@std/encoding@0.218.2",
"jsr:@std/fmt@^0.218.2": "jsr:@std/fmt@0.218.2",
"jsr:@std/http@0.218": "jsr:@std/http@0.218.2",
"jsr:@std/io@0.218": "jsr:@std/io@0.218.2",
"jsr:@std/media-types@0.218": "jsr:@std/media-types@0.218.2",
"jsr:@std/path@0.218": "jsr:@std/path@0.218.2",
"npm:@types/node": "npm:@types/node@18.16.19",
"npm:@types/sdp-transform": "npm:@types/sdp-transform@2.4.9",
"npm:path-to-regexp@6.2.1": "npm:path-to-regexp@6.2.1",
"npm:sdp-transform@2.14.2": "npm:sdp-transform@2.14.2"
},
"jsr": {
"@earthstar/dns-sd@3.1.0": {
"integrity": "8b76b7ccfe230677f6177227e0250bff350b3b19604b3d8c534c1e2e3318d411",
"dependencies": [
"jsr:@std/bytes@^0.224.0"
]
},
"@oak/commons@0.7.0": {
"integrity": "4bd889b3dc9ddac1b602034d88c137f06de7078775961b51081beb5f175c120b"
},
"@oak/oak@14.2.0": {
"integrity": "b683b089693004ac3bca80b52159b3e9ad214dc8246ff5dc61ba658da78bc166",
"dependencies": [
"jsr:@oak/commons@0.7",
"jsr:@std/assert@0.218",
"jsr:@std/bytes@0.218",
"jsr:@std/crypto@0.218",
"jsr:@std/encoding@0.218",
"jsr:@std/http@0.218",
"jsr:@std/io@0.218",
"jsr:@std/media-types@0.218",
"jsr:@std/path@0.218",
"npm:path-to-regexp@6.2.1"
]
},
"@std/assert@0.218.2": {
"integrity": "7f0a5a1a8cf86607cd6c2c030584096e1ffad27fc9271429a8cb48cfbdee5eaf",
"dependencies": [
"jsr:@std/fmt@^0.218.2"
]
},
"@std/bytes@0.218.2": {
"integrity": "91fe54b232dcca73856b79a817247f4a651dbb60d51baafafb6408c137241670"
},
"@std/bytes@0.224.0": {
"integrity": "a2250e1d0eb7d1c5a426f21267ab9bdeac2447fa87a3d0d1a467d3f7a6058e49"
},
"@std/crypto@0.218.2": {
"integrity": "8c5031a3a1c3ac3bed3c0d4bed2fe7e7faedcb673bbfa0edd10570c8452f5cd2",
"dependencies": [
"jsr:@std/assert@^0.218.2",
"jsr:@std/encoding@^0.218.2"
]
},
"@std/encoding@0.218.2": {
"integrity": "da55a763c29bf0dbf06fd286430b358266eb99c28789d89fe9a3e28edecb8d8e"
},
"@std/fmt@0.218.2": {
"integrity": "99526449d2505aa758b6cbef81e7dd471d8b28ec0dcb1491d122b284c548788a"
},
"@std/http@0.218.2": {
"integrity": "54223b62702e665b9dab6373ea2e51235e093ef47228d21cfa0469ee5ac75c9b",
"dependencies": [
"jsr:@std/assert@^0.218.2",
"jsr:@std/encoding@^0.218.2"
]
},
"@std/io@0.218.2": {
"integrity": "c64fbfa087b7c9d4d386c5672f291f607d88cb7d44fc299c20c713e345f2785f",
"dependencies": [
"jsr:@std/bytes@^0.218.2"
]
},
"@std/media-types@0.218.2": {
"integrity": "1ed3bd2a05e44bad3fc2bab1767d0ce7f2fd68baee62a980751ce51633acb788"
},
"@std/path@0.218.2": {
"integrity": "b568fd923d9e53ad76d17c513e7310bda8e755a3e825e6289a0ce536404e2662",
"dependencies": [
"jsr:@std/assert@^0.218.2"
]
}
},
"npm": {
"@types/node@18.16.19": {
"integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==",
"dependencies": {}
},
"@types/sdp-transform@2.4.9": {
"integrity": "sha512-bVr+/OoZZy7wrHlNcEAAa6PAgKA4BoXPYVN2EijMC5WnGgQ4ZEuixmKnVs2roiAvr7RhIFVH17QD27cojgIZCg==",
"dependencies": {}
},
"path-to-regexp@6.2.1": {
"integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==",
"dependencies": {}
},
"sdp-transform@2.14.2": {
"integrity": "sha512-icY6jVao7MfKCieyo1AyxFYm1baiM+fA00qW/KrNNVlkxHAd34riEKuEkUe4bBb3gJwLJZM+xT60Yj1QL8rHiA==",
"dependencies": {}
}
}
},
"redirects": {
"https://deno.land/std/testing/asserts.ts": "https://deno.land/std@0.224.0/testing/asserts.ts",
"https://deno.land/x/port/mod.ts": "https://deno.land/x/port@1.0.0/mod.ts"
},
"remote": {
"https://deno.land/std@0.224.0/assert/_constants.ts": "a271e8ef5a573f1df8e822a6eb9d09df064ad66a4390f21b3e31f820a38e0975",
"https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834",
"https://deno.land/std@0.224.0/assert/assert_almost_equals.ts": "9e416114322012c9a21fa68e187637ce2d7df25bcbdbfd957cd639e65d3cf293",
"https://deno.land/std@0.224.0/assert/assert_array_includes.ts": "14c5094471bc8e4a7895fc6aa5a184300d8a1879606574cb1cd715ef36a4a3c7",
"https://deno.land/std@0.224.0/assert/assert_equals.ts": "3bbca947d85b9d374a108687b1a8ba3785a7850436b5a8930d81f34a32cb8c74",
"https://deno.land/std@0.224.0/assert/assert_exists.ts": "43420cf7f956748ae6ed1230646567b3593cb7a36c5a5327269279c870c5ddfd",
"https://deno.land/std@0.224.0/assert/assert_false.ts": "3e9be8e33275db00d952e9acb0cd29481a44fa0a4af6d37239ff58d79e8edeff",
"https://deno.land/std@0.224.0/assert/assert_greater.ts": "5e57b201fd51b64ced36c828e3dfd773412c1a6120c1a5a99066c9b261974e46",
"https://deno.land/std@0.224.0/assert/assert_greater_or_equal.ts": "9870030f997a08361b6f63400273c2fb1856f5db86c0c3852aab2a002e425c5b",
"https://deno.land/std@0.224.0/assert/assert_instance_of.ts": "e22343c1fdcacfaea8f37784ad782683ec1cf599ae9b1b618954e9c22f376f2c",
"https://deno.land/std@0.224.0/assert/assert_is_error.ts": "f856b3bc978a7aa6a601f3fec6603491ab6255118afa6baa84b04426dd3cc491",
"https://deno.land/std@0.224.0/assert/assert_less.ts": "60b61e13a1982865a72726a5fa86c24fad7eb27c3c08b13883fb68882b307f68",
"https://deno.land/std@0.224.0/assert/assert_less_or_equal.ts": "d2c84e17faba4afe085e6c9123a63395accf4f9e00150db899c46e67420e0ec3",
"https://deno.land/std@0.224.0/assert/assert_match.ts": "ace1710dd3b2811c391946954234b5da910c5665aed817943d086d4d4871a8b7",
"https://deno.land/std@0.224.0/assert/assert_not_equals.ts": "78d45dd46133d76ce624b2c6c09392f6110f0df9b73f911d20208a68dee2ef29",
"https://deno.land/std@0.224.0/assert/assert_not_instance_of.ts": "3434a669b4d20cdcc5359779301a0588f941ffdc2ad68803c31eabdb4890cf7a",
"https://deno.land/std@0.224.0/assert/assert_not_match.ts": "df30417240aa2d35b1ea44df7e541991348a063d9ee823430e0b58079a72242a",
"https://deno.land/std@0.224.0/assert/assert_not_strict_equals.ts": "37f73880bd672709373d6dc2c5f148691119bed161f3020fff3548a0496f71b8",
"https://deno.land/std@0.224.0/assert/assert_object_match.ts": "411450fd194fdaabc0089ae68f916b545a49d7b7e6d0026e84a54c9e7eed2693",
"https://deno.land/std@0.224.0/assert/assert_rejects.ts": "4bee1d6d565a5b623146a14668da8f9eb1f026a4f338bbf92b37e43e0aa53c31",
"https://deno.land/std@0.224.0/assert/assert_strict_equals.ts": "b4f45f0fd2e54d9029171876bd0b42dd9ed0efd8f853ab92a3f50127acfa54f5",
"https://deno.land/std@0.224.0/assert/assert_string_includes.ts": "496b9ecad84deab72c8718735373feb6cdaa071eb91a98206f6f3cb4285e71b8",
"https://deno.land/std@0.224.0/assert/assert_throws.ts": "c6508b2879d465898dab2798009299867e67c570d7d34c90a2d235e4553906eb",
"https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917",
"https://deno.land/std@0.224.0/assert/equal.ts": "bddf07bb5fc718e10bb72d5dc2c36c1ce5a8bdd3b647069b6319e07af181ac47",
"https://deno.land/std@0.224.0/assert/fail.ts": "0eba674ffb47dff083f02ced76d5130460bff1a9a68c6514ebe0cdea4abadb68",
"https://deno.land/std@0.224.0/assert/mod.ts": "48b8cb8a619ea0b7958ad7ee9376500fe902284bb36f0e32c598c3dc34cbd6f3",
"https://deno.land/std@0.224.0/assert/unimplemented.ts": "8c55a5793e9147b4f1ef68cd66496b7d5ba7a9e7ca30c6da070c1a58da723d73",
"https://deno.land/std@0.224.0/assert/unreachable.ts": "5ae3dbf63ef988615b93eb08d395dda771c96546565f9e521ed86f6510c29e19",
"https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5",
"https://deno.land/std@0.224.0/internal/diff.ts": "6234a4b493ebe65dc67a18a0eb97ef683626a1166a1906232ce186ae9f65f4e6",
"https://deno.land/std@0.224.0/internal/format.ts": "0a98ee226fd3d43450245b1844b47003419d34d210fa989900861c79820d21c2",
"https://deno.land/std@0.224.0/internal/mod.ts": "534125398c8e7426183e12dc255bb635d94e06d0f93c60a297723abe69d3b22e",
"https://deno.land/std@0.224.0/testing/asserts.ts": "d0cdbabadc49cc4247a50732ee0df1403fdcd0f95360294ad448ae8c240f3f5c",
"https://deno.land/x/free_port@v1.2.0/mod.ts": "512646732aaea41fbfd1f210f3ae82660f38251777d189d290da331d0235a58e",
"https://deno.land/x/port@1.0.0/mod.ts": "2dc04ce1ccf133ae09205e30b550044c4c6f64a1a7d00ea91c66dbb9f6cc00f5",
"https://deno.land/x/port@1.0.0/types.ts": "42d6ae4147d5d67408d60209da070ddfa79ec8389c6cab1b8002df0cf6c03af6"
},
"workspace": {
"dependencies": [
"jsr:@earthstar/dns-sd@^3.1.0"
]
}
}

View File

@@ -1,8 +0,0 @@
export function add(a: number, b: number): number {
return a + b;
}
// Learn more at https://deno.land/manual/examples/module_metadata#concepts
if (import.meta.main) {
console.log("Add 2 + 3 =", add(2, 3));
}

10
gobot-gui/api/output.sdp Normal file
View File

@@ -0,0 +1,10 @@
v=0
o=- 0 0 IN IP4 127.0.0.1
s=No Name
c=IN IP4 127.0.0.1
t=0 0
a=tool:libavformat 60.16.100
m=video 1234 RTP/AVP 96
b=AS:3000
a=rtpmap:96 MP4V-ES/90000
a=fmtp:96 profile-level-id=1

View File

@@ -0,0 +1,12 @@
#/usr/bin/bash
SDP_FILE="output.sdp"
ffmpeg \
-stream_loop -1 \
-rtbufsize 2M \
-re \
-i "test/Big_Buck_Bunny_1080_10s_10MB.mp4" \
-b:v 3M \
-sdp_file $SDP_FILE \
-f rtp rtp://127.0.0.1:1234

View File

@@ -0,0 +1,48 @@
export interface IceCandidate {
foundation: string;
componentId: number;
transport: string;
priority: number;
ipAddress: string;
port: number;
candidateType: string;
relatedAddress: string | null;
relatedPort: number | null;
}
export function parseIceCandidate(candidate: string): IceCandidate {
const parts = candidate.split(' ');
const iceCandidate: IceCandidate = {
foundation: parts[0].split(':')[1],
componentId: Number.parseInt(parts[1]),
transport: parts[2],
priority: Number.parseInt(parts[3]),
ipAddress: parts[4],
port: Number.parseInt(parts[5]),
candidateType: parts[7],
relatedAddress: null,
relatedPort: null
};
for (let i = 8; i < parts.length; i++) {
if (parts[i] === 'raddr') {
iceCandidate.relatedAddress = parts[i + 1];
} else if (parts[i] === 'rport') {
iceCandidate.relatedPort = Number.parseInt(parts[i + 1]);
}
}
return iceCandidate;
}
export function findHighestPriorityCandidate(candidates: IceCandidate[]): IceCandidate | null {
if (candidates.length === 0) {
return null;
}
return candidates.reduce((highest, candidate) =>
candidate.priority > highest.priority ? candidate : highest
);
}

145
gobot-gui/api/src/main.ts Normal file
View File

@@ -0,0 +1,145 @@
import { Application,Router } from "jsr:@oak/oak@14";
import { crypto } from "jsr:@std/crypto";
import { randomBytes } from "node:crypto";
import { encodeHex } from "jsr:@std/encoding/hex";
// @deno-types="npm:@types/sdp-transform"
import sdpTransform from "npm:sdp-transform@2.14.2";
import { IceCandidate, findHighestPriorityCandidate, parseIceCandidate } from "./iceCandiate.ts";
import { browse, MulticastInterface } from "jsr:@earthstar/dns-sd";
const app = new Application();
const router = new Router({
prefix: "/api",
});
const rtpIncommingConnection = Deno.listenDatagram({ port: 1234, transport: "udp" });
let outgoingAddresses: Map<string, Deno.Addr> = new Map();
(async () => {
for await (const [data, addr] of rtpIncommingConnection) {
//console.log(data.length);
outgoingAddresses.forEach((outgoingAddr) => {
rtpIncommingConnection.send(data, outgoingAddr);
});
}
})()
async function getFingerprint(): Promise<string> {
const cert = randomBytes(32);
const hash = encodeHex(await crypto.subtle.digest("SHA-256", cert));
return hash.split(/(..)/g).filter(s => s !== "").join(":");
}
async function resolveMDNS(address: string): Promise<string | undefined> {
const pingCommand = new Deno.Command("timeout", {
args: ["2", "ping", "-c", "1", address],
});
const { code, stdout, stderr } = await pingCommand.output();
if (code !== 0) {
return undefined;
}
const realAddr = (new TextDecoder().decode(stdout))
.match(/\((.*)\):/)?.at(1);
return realAddr
}
async function findWorkingIceCandiate(candidates: IceCandidate[]): Promise<{ candiate: IceCandidate, ipAddress: string } | undefined> {
const workingCandiates: {
candiate: IceCandidate,
ipAddress: string,
priority: number
}[] = [];
candidates = candidates.sort((a, b) => a.priority - b.priority);
for (const candidate of candidates) {
if(candidate.ipAddress === undefined || candidate.transport.toLowerCase() !== "udp") continue;
const ipAddr = await resolveMDNS(candidate.ipAddress);
console.log(ipAddr, candidate.ipAddress);
if (ipAddr) {
workingCandiates.push({
candiate: candidate,
ipAddress: ipAddr,
priority: candidate.priority
});
break;
}
}
if (workingCandiates.length === 0) {
return undefined;
} else {
return workingCandiates.reduce((highest, candidate) =>
candidate.priority > highest.priority ? candidate : highest
);
}
}
router.post("/webrtc-offer", async (ctx) => {
const body = await ctx.request.body.json();
const clientSdp = sdpTransform.parse(body.offer.sdp);
const clientIceRaw: {
candidate: string,
sdpMid: string,
sdpMLineIndex: number
usernameFragment: string
}[] = body.ice;
const clientIce = clientIceRaw.map((ice) => parseIceCandidate(ice.candidate));
const remoteIceCandiate = await findWorkingIceCandiate(clientIce);
if (!remoteIceCandiate) {
ctx.response.status = 400;
ctx.response.type = "application/json";
ctx.response.body = { error: "None of the provided IceCandiates where reachable" };
return;
}
const clientAddr: Deno.NetAddr = {
transport: "udp",
hostname: remoteIceCandiate.ipAddress,
port: remoteIceCandiate.candiate.port
};
console.log(clientAddr);
const ffmpegSdp = sdpTransform.parse(await Deno.readTextFile("./output.sdp"));
ffmpegSdp.media[0].fingerprint = {
type: "sha-256",
hash: await getFingerprint()
}
ffmpegSdp.iceUfrag = encodeHex(randomBytes(32));
ffmpegSdp.icePwd = encodeHex(randomBytes(32));
ffmpegSdp.media[0].fmtp = [];
ffmpegSdp.
//ffmpegSdp.media[0].rtcpMux = "";
const sessionId = clientSdp.origin?.sessionId;
if (!sessionId) {
ctx.response.status = 400;
ctx.response.type = "application/json";
ctx.response.body = { error: "sessionId is undefined" };
return;
}
outgoingAddresses.set(sessionId.toString(), clientAddr);
ctx.response.type = "application/json";
ctx.response.body = {
"type": "answer",
"sdp": sdpTransform.write(ffmpegSdp)
}
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen({ port: 3000 });

Binary file not shown.

View File

@@ -0,0 +1,114 @@
import { assertEquals } from "jsr:@std/assert";
import { IceCandidate, parseIceCandidate } from '../src/iceCandiate.ts';
const iceCandidates = [
"candidate:842163049 1 udp 1677729535 192.168.1.2 3478 typ host",
"candidate:842163049 2 udp 1677729534 192.168.1.3 3479 typ srflx raddr 192.168.1.2 rport 3478",
"candidate:842163049 1 tcp 1677729533 192.168.1.4 3480 typ relay raddr 192.168.1.3 rport 3479",
"candidate:842163049 2 tcp 1677729532 192.168.1.5 3481 typ host",
"candidate:842163049 1 udp 1677729531 192.168.1.6 3482 typ srflx rport 3480",
"candidate:842163049 2 udp 1677729530 192.168.1.7 3483 typ relay raddr 192.168.1.5",
"candidate:842163049 1 tcp 1677729529 192.168.1.8 3484 typ host",
"candidate:842163049 2 tcp 1677729528 192.168.1.9 3485 typ srflx raddr 192.168.1.6 rport 3482"
];
const parsedExamples: IceCandidate[] = [
{
foundation: "842163049",
componentId: 1,
transport: "udp",
priority: 1677729535,
ipAddress: "192.168.1.2",
port: 3478,
candidateType: "host",
relatedAddress: null,
relatedPort: null
},
{
foundation: "842163049",
componentId: 2,
transport: "udp",
priority: 1677729534,
ipAddress: "192.168.1.3",
port: 3479,
candidateType: "srflx",
relatedAddress: "192.168.1.2",
relatedPort: 3478
},
{
foundation: "842163049",
componentId: 1,
transport: "tcp",
priority: 1677729533,
ipAddress: "192.168.1.4",
port: 3480,
candidateType: "relay",
relatedAddress: "192.168.1.3",
relatedPort: 3479
},
{
foundation: "842163049",
componentId: 2,
transport: "tcp",
priority: 1677729532,
ipAddress: "192.168.1.5",
port: 3481,
candidateType: "host",
relatedAddress: null,
relatedPort: null
},
{
foundation: "842163049",
componentId: 1,
transport: "udp",
priority: 1677729531,
ipAddress: "192.168.1.6",
port: 3482,
candidateType: "srflx",
relatedPort: 3480,
relatedAddress: null
},
{
foundation: "842163049",
componentId: 2,
transport: "udp",
priority: 1677729530,
ipAddress: "192.168.1.7",
port: 3483,
candidateType: "relay",
relatedAddress: "192.168.1.5",
relatedPort: null
},
{
foundation: "842163049",
componentId: 1,
transport: "tcp",
priority: 1677729529,
ipAddress: "192.168.1.8",
port: 3484,
candidateType: "host",
relatedAddress: null,
relatedPort: null
},
{
foundation: "842163049",
componentId: 2,
transport: "tcp",
priority: 1677729528,
ipAddress: "192.168.1.9",
port: 3485,
candidateType: "srflx",
relatedAddress: "192.168.1.6",
relatedPort: 3482
}
];
Deno.test("parseIceCandidate should correctly parse ICE candidates", async (t) => {
for (const [index, candidate] of iceCandidates.entries()) {
await t.step(`${index}-${candidate}`, () => {
const parsed = parseIceCandidate(candidate);
console.log(candidate);
assertEquals(parsed, parsedExamples[index]);
})
}
});

View File

@@ -0,0 +1,111 @@
<script lang="ts">
import { onMount } from "svelte";
let videoElementIn: HTMLVideoElement;
let videoElementOut: HTMLVideoElement;
let peerConnection: RTCPeerConnection;
let incomingStream: MediaStream;
let outgoingStream: MediaStream;
let offerBase64Out: string;
let offerBase64In: string;
let answerBase64Out: string;
let answerBase64In: string;
let iceCandidate: RTCIceCandidate[] = [];
onMount(() => {
})
async function getIce(d: RTCSessionDescription | RTCSessionDescriptionInit): Promise<RTCIceCandidate[]> {
let icecandidate: RTCIceCandidate[] = [];
peerConnection.addEventListener("icecandidate", (event) => {
if(event.candidate) {
icecandidate.push(event.candidate);
}
});
await peerConnection.setLocalDescription(d);
console.log("2asd");
await new Promise((resolve) => {
if(peerConnection.iceGatheringState === 'complete') {
resolve(null);
} else {
peerConnection.addEventListener('icegatheringstatechange', () => {
console.log(peerConnection.iceGatheringState);
if(peerConnection.iceGatheringState === 'complete') {
resolve(null);
}
});
}
});
console.log("3asd");
return icecandidate;
}
async function start_rx() {
peerConnection = new RTCPeerConnection({
certificates: [],
iceServers: [],
iceTransportPolicy: "all",
// @ts-ignore
rtcpMuxPolicy: 'negotiate' // Workaround, typescript doesn't know about this property
});
const offer = await peerConnection.createOffer();
iceCandidate = await getIce(offer);
console.log("asd");
console.log(iceCandidate);
}
async function start_tx() {
console.log("Starting TX");
peerConnection = new RTCPeerConnection({
certificates: [],
iceServers: [],
iceTransportPolicy: "all",
// @ts-ignore
rtcpMuxPolicy: 'negotiate' // Workaround, typescript doesn't know about this property
});
iceCandidate = await getIce();
}
async function copy_to_clip_board(s: string) {
await navigator.clipboard.writeText(s);
console.log("Copied to clipboard");
}
</script>
<main>
<video bind:this={videoElementIn} autoplay playsinline></video>
<video bind:this={videoElementOut} autoplay playsinline></video>
<button on:click={start_tx}>Start TX</button>
<button on:click={start_rx}>Start RX</button>
<br>
<p>Offer: {offerBase64Out}</p>
<button on:click={() => copy_to_clip_board(offerBase64Out)}>Copy Offer</button>
<input type="text" bind:value={offerBase64In}>
<br>
<p>Answere: {answerBase64Out}</p>
<button on:click={() => copy_to_clip_board(answerBase64Out)}>Copy Answer</button>
<input type="text" bind:value={offerBase64In}>
<br>
<p>Ice Candiates</p>
{#each iceCandidate as ice}
<p>{ice.address}, {ice.port}, {ice.protocol}</p>
{/each}
</main>

View File

@@ -3,11 +3,25 @@
let peerConnection: RTCPeerConnection;
let videoElement: HTMLVideoElement;
let incommingStream: MediaStream;
async function negotiateWebRTC(peerConn: RTCPeerConnection) {
incommingStream = new MediaStream();
videoElement.srcObject = incommingStream;
peerConn.addTransceiver('video', { direction: 'recvonly' });
const offer = await peerConn.createOffer();
const offer = await peerConn.createOffer({
offerToReceiveVideo: true
});
let icecandidate: RTCIceCandidate[] = [];
peerConnection.addEventListener("icecandidate", (event) => {
if(event.candidate) {
icecandidate.push(event.candidate);
}
});
// This does ICE gathering
await peerConn.setLocalDescription(offer);
await new Promise((resolve) => {
@@ -15,9 +29,9 @@
resolve(null);
} else {
peerConn.addEventListener('icegatheringstatechange', () => {
console.log(peerConn.iceGatheringState);
if(peerConn.iceGatheringState === 'complete')
if(peerConn.iceGatheringState === 'complete') {
resolve(null);
}
});
}
});
@@ -27,34 +41,50 @@
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ offer })
body: JSON.stringify({ offer, ice: icecandidate})
})).json();
//console.log(remoteOffer.sdp);
await peerConn.setRemoteDescription(remoteOffer);
}
onMount(async () => {
peerConnection = new RTCPeerConnection({
iceServers: []
certificates: [],
iceServers: [],
iceTransportPolicy: "all",
// @ts-ignore
rtcpMuxPolicy: 'negotiate' // Workaround, typescript doesn't know about this property
});
peerConnection.addEventListener('track', (event) => {
console.log('track', event);
videoElement.srcObject = event.streams[0];
event.streams[0].getTracks().forEach(track => {
incommingStream.addTrack(track);
});
});
peerConnection.addEventListener("icecandidate", (event) => {
if(event.candidate) {
console.log(event.candidate.candidate);
}
});
await negotiateWebRTC(peerConnection);
peerConnection.onconnectionstatechange = () => {
console.log("PC State:", peerConnection.connectionState);
}
});
function start_playing() {
videoElement.play();
}
</script>
<main>
<video bind:this={videoElement}></video>
<!-- svelte-ignore a11y-media-has-caption -->
<video bind:this={videoElement} autoplay playsinline></video>
<button>stop</button>
<button>start</button>
<button on:click={start_playing}>start</button>
</main>
<style lang="scss">

View File

@@ -1,6 +1,11 @@
<script>
import Feedtes from "$lib/Feedtes.svelte";
import LiveFeed from "$lib/LiveFeed.svelte";
</script>
<Feedtes/>
<!--
<LiveFeed/>
-->

BIN
gobot-gui/webrtc_test/.gitignore (Stored with Git LFS) vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
engine-strict=true

BIN
gobot-gui/webrtc_test/README.md (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -0,0 +1,3 @@
{
"deno.enable": true
}

View File

View File

@@ -0,0 +1,5 @@
{
"tasks": {
"dev": "deno run -A --unstable-net --watch main.ts"
}
}

104
gobot-gui/webrtc_test/api/deno.lock generated Normal file
View File

@@ -0,0 +1,104 @@
{
"version": "3",
"packages": {
"specifiers": {
"jsr:@oak/commons@0.7": "jsr:@oak/commons@0.7.0",
"jsr:@oak/oak@14": "jsr:@oak/oak@14.2.0",
"jsr:@std/assert@0.218": "jsr:@std/assert@0.218.2",
"jsr:@std/assert@^0.218.2": "jsr:@std/assert@0.218.2",
"jsr:@std/bytes@0.218": "jsr:@std/bytes@0.218.2",
"jsr:@std/bytes@^0.218.2": "jsr:@std/bytes@0.218.2",
"jsr:@std/crypto@0.218": "jsr:@std/crypto@0.218.2",
"jsr:@std/encoding@0.218": "jsr:@std/encoding@0.218.2",
"jsr:@std/encoding@^0.218.2": "jsr:@std/encoding@0.218.2",
"jsr:@std/http@0.218": "jsr:@std/http@0.218.2",
"jsr:@std/io@0.218": "jsr:@std/io@0.218.2",
"jsr:@std/media-types@0.218": "jsr:@std/media-types@0.218.2",
"jsr:@std/path@0.218": "jsr:@std/path@0.218.2",
"npm:@types/node": "npm:@types/node@18.16.19",
"npm:@types/sdp-transform": "npm:@types/sdp-transform@2.4.9",
"npm:path-to-regexp@6.2.1": "npm:path-to-regexp@6.2.1",
"npm:sdp-transform@2.14.2": "npm:sdp-transform@2.14.2"
},
"jsr": {
"@oak/commons@0.7.0": {
"integrity": "4bd889b3dc9ddac1b602034d88c137f06de7078775961b51081beb5f175c120b"
},
"@oak/oak@14.2.0": {
"integrity": "b683b089693004ac3bca80b52159b3e9ad214dc8246ff5dc61ba658da78bc166",
"dependencies": [
"jsr:@oak/commons@0.7",
"jsr:@std/assert@0.218",
"jsr:@std/bytes@0.218",
"jsr:@std/crypto@0.218",
"jsr:@std/encoding@0.218",
"jsr:@std/http@0.218",
"jsr:@std/io@0.218",
"jsr:@std/media-types@0.218",
"jsr:@std/path@0.218",
"npm:path-to-regexp@6.2.1"
]
},
"@std/assert@0.218.2": {
"integrity": "7f0a5a1a8cf86607cd6c2c030584096e1ffad27fc9271429a8cb48cfbdee5eaf"
},
"@std/bytes@0.218.2": {
"integrity": "91fe54b232dcca73856b79a817247f4a651dbb60d51baafafb6408c137241670"
},
"@std/crypto@0.218.2": {
"integrity": "8c5031a3a1c3ac3bed3c0d4bed2fe7e7faedcb673bbfa0edd10570c8452f5cd2",
"dependencies": [
"jsr:@std/assert@^0.218.2",
"jsr:@std/encoding@^0.218.2"
]
},
"@std/encoding@0.218.2": {
"integrity": "da55a763c29bf0dbf06fd286430b358266eb99c28789d89fe9a3e28edecb8d8e"
},
"@std/http@0.218.2": {
"integrity": "54223b62702e665b9dab6373ea2e51235e093ef47228d21cfa0469ee5ac75c9b",
"dependencies": [
"jsr:@std/assert@^0.218.2",
"jsr:@std/encoding@^0.218.2"
]
},
"@std/io@0.218.2": {
"integrity": "c64fbfa087b7c9d4d386c5672f291f607d88cb7d44fc299c20c713e345f2785f",
"dependencies": [
"jsr:@std/bytes@^0.218.2"
]
},
"@std/media-types@0.218.2": {
"integrity": "1ed3bd2a05e44bad3fc2bab1767d0ce7f2fd68baee62a980751ce51633acb788"
},
"@std/path@0.218.2": {
"integrity": "b568fd923d9e53ad76d17c513e7310bda8e755a3e825e6289a0ce536404e2662",
"dependencies": [
"jsr:@std/assert@^0.218.2"
]
}
},
"npm": {
"@types/node@18.16.19": {
"integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==",
"dependencies": {}
},
"@types/sdp-transform@2.4.9": {
"integrity": "sha512-bVr+/OoZZy7wrHlNcEAAa6PAgKA4BoXPYVN2EijMC5WnGgQ4ZEuixmKnVs2roiAvr7RhIFVH17QD27cojgIZCg==",
"dependencies": {}
},
"path-to-regexp@6.2.1": {
"integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==",
"dependencies": {}
},
"sdp-transform@2.14.2": {
"integrity": "sha512-icY6jVao7MfKCieyo1AyxFYm1baiM+fA00qW/KrNNVlkxHAd34riEKuEkUe4bBb3gJwLJZM+xT60Yj1QL8rHiA==",
"dependencies": {}
}
}
},
"remote": {
"https://deno.land/std@0.224.0/encoding/_util.ts": "beacef316c1255da9bc8e95afb1fa56ed69baef919c88dc06ae6cb7a6103d376",
"https://deno.land/std@0.224.0/encoding/hex.ts": "6270f25e5d85f99fcf315278670ba012b04b7c94b67715b53f30d03249687c07"
}
}

View File

@@ -0,0 +1,66 @@
import { Application, Router } from "jsr:@oak/oak@14";
import { router_webrtc } from "./webrtc_fake.ts";
const app = new Application();
const router = new Router({
prefix: "/api",
});
let offer: any = {};
let answer: any = {};
router.post("/offer", async (ctx) => {
const body = await ctx.request.body.json();
if (body) {
offer = body;
ctx.response.status = 200;
ctx.response.body = { message: "Answer stored successfully" };
} else {
ctx.response.status = 400;
ctx.response.body = { error: "Invalid JSON" };
}
});
router.post("/answer", async (ctx) => {
const body = await ctx.request.body.json();
if (body) {
answer = body;
ctx.response.status = 200;
ctx.response.body = { message: "Answer stored successfully" };
} else {
ctx.response.status = 400;
ctx.response.body = { error: "Invalid JSON" };
}
});
router.get("/offer", (ctx) => {
ctx.response.body = offer;
if(Object.keys(answer).length === 0) {
ctx.response.status = 204;
} else {
offer = {};
ctx.response.status = 200;
}
ctx.response.headers.set("Content-Type", "application/json");
});
router.get("/answer", (ctx) => {
ctx.response.body = answer;
if(Object.keys(answer).length === 0) {
ctx.response.status = 204;
} else {
answer = {};
ctx.response.status = 200;
}
ctx.response.headers.set("Content-Type", "application/json");
});
app.use(router_webrtc.routes());
app.use(router_webrtc.allowedMethods());
app.use(router.routes());
app.use(router.allowedMethods());
app.listen({
port: 8080,
});

View File

@@ -0,0 +1,8 @@
v=0
o=- 0 0 IN IP4 127.0.0.1
s=No Name
t=0 0
m=video 1234 RTP/AVP 96
b=AS:3000
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1

View File

@@ -0,0 +1,14 @@
#/usr/bin/bash
SDP_FILE="output.sdp"
ffmpeg \
-stream_loop -1 \
-rtbufsize 2M \
-re \
-i "test/Big_Buck_Bunny_1080_10s_10MB.mp4" \
-b:v 3M \
-sdp_file $SDP_FILE \
-vcodec libx264 \
-acodec aac \
-f rtp rtp://0.0.0.0:1234

View File

@@ -0,0 +1,48 @@
export interface IceCandidate {
foundation: string;
componentId: number;
transport: string;
priority: number;
ipAddress: string;
port: number;
candidateType: string;
relatedAddress: string | null;
relatedPort: number | null;
}
export function parseIceCandidate(candidate: string): IceCandidate {
const parts = candidate.split(' ');
const iceCandidate: IceCandidate = {
foundation: parts[0].split(':')[1],
componentId: Number.parseInt(parts[1]),
transport: parts[2],
priority: Number.parseInt(parts[3]),
ipAddress: parts[4],
port: Number.parseInt(parts[5]),
candidateType: parts[7],
relatedAddress: null,
relatedPort: null
};
for (let i = 8; i < parts.length; i++) {
if (parts[i] === 'raddr') {
iceCandidate.relatedAddress = parts[i + 1];
} else if (parts[i] === 'rport') {
iceCandidate.relatedPort = Number.parseInt(parts[i + 1]);
}
}
return iceCandidate;
}
export function findHighestPriorityCandidate(candidates: IceCandidate[]): IceCandidate | null {
if (candidates.length === 0) {
return null;
}
return candidates.reduce((highest, candidate) =>
candidate.priority > highest.priority ? candidate : highest
);
}

View File

@@ -0,0 +1,114 @@
import { assertEquals } from "jsr:@std/assert";
import { IceCandidate, parseIceCandidate } from '../src/iceCandiate.ts';
const iceCandidates = [
"candidate:842163049 1 udp 1677729535 192.168.1.2 3478 typ host",
"candidate:842163049 2 udp 1677729534 192.168.1.3 3479 typ srflx raddr 192.168.1.2 rport 3478",
"candidate:842163049 1 tcp 1677729533 192.168.1.4 3480 typ relay raddr 192.168.1.3 rport 3479",
"candidate:842163049 2 tcp 1677729532 192.168.1.5 3481 typ host",
"candidate:842163049 1 udp 1677729531 192.168.1.6 3482 typ srflx rport 3480",
"candidate:842163049 2 udp 1677729530 192.168.1.7 3483 typ relay raddr 192.168.1.5",
"candidate:842163049 1 tcp 1677729529 192.168.1.8 3484 typ host",
"candidate:842163049 2 tcp 1677729528 192.168.1.9 3485 typ srflx raddr 192.168.1.6 rport 3482"
];
const parsedExamples: IceCandidate[] = [
{
foundation: "842163049",
componentId: 1,
transport: "udp",
priority: 1677729535,
ipAddress: "192.168.1.2",
port: 3478,
candidateType: "host",
relatedAddress: null,
relatedPort: null
},
{
foundation: "842163049",
componentId: 2,
transport: "udp",
priority: 1677729534,
ipAddress: "192.168.1.3",
port: 3479,
candidateType: "srflx",
relatedAddress: "192.168.1.2",
relatedPort: 3478
},
{
foundation: "842163049",
componentId: 1,
transport: "tcp",
priority: 1677729533,
ipAddress: "192.168.1.4",
port: 3480,
candidateType: "relay",
relatedAddress: "192.168.1.3",
relatedPort: 3479
},
{
foundation: "842163049",
componentId: 2,
transport: "tcp",
priority: 1677729532,
ipAddress: "192.168.1.5",
port: 3481,
candidateType: "host",
relatedAddress: null,
relatedPort: null
},
{
foundation: "842163049",
componentId: 1,
transport: "udp",
priority: 1677729531,
ipAddress: "192.168.1.6",
port: 3482,
candidateType: "srflx",
relatedPort: 3480,
relatedAddress: null
},
{
foundation: "842163049",
componentId: 2,
transport: "udp",
priority: 1677729530,
ipAddress: "192.168.1.7",
port: 3483,
candidateType: "relay",
relatedAddress: "192.168.1.5",
relatedPort: null
},
{
foundation: "842163049",
componentId: 1,
transport: "tcp",
priority: 1677729529,
ipAddress: "192.168.1.8",
port: 3484,
candidateType: "host",
relatedAddress: null,
relatedPort: null
},
{
foundation: "842163049",
componentId: 2,
transport: "tcp",
priority: 1677729528,
ipAddress: "192.168.1.9",
port: 3485,
candidateType: "srflx",
relatedAddress: "192.168.1.6",
relatedPort: 3482
}
];
Deno.test("parseIceCandidate should correctly parse ICE candidates", async (t) => {
for (const [index, candidate] of iceCandidates.entries()) {
await t.step(`${index}-${candidate}`, () => {
const parsed = parseIceCandidate(candidate);
console.log(candidate);
assertEquals(parsed, parsedExamples[index]);
})
}
});

View File

@@ -0,0 +1,145 @@
import { Router } from "jsr:@oak/oak@14";
// @deno-types="npm:@types/sdp-transform"
import sdpTransform from "npm:sdp-transform@2.14.2";
import { encodeHex,} from "https://deno.land/std@0.224.0/encoding/hex.ts";
import { parseIceCandidate, IceCandidate } from "./src/iceCandiate.ts";
const router = new Router({
prefix: "/api",
});
export {router as router_webrtc};
let rxAddress: Deno.Addr | undefined = undefined;
const ffmpegSocket = Deno.listenDatagram({
hostname: "localhost",
port: 1234,
transport: "udp"
});
let ffmpegRxRun = true;
// deno-lint-ignore no-async-promise-executor
const _ffmpegRxPromose = new Promise(async (resolve, _reject) => {
while(ffmpegRxRun) {
const data = new Uint8Array(65536);
await ffmpegSocket.receive(data);
if(rxAddress !== undefined) {
await ffmpegSocket.send(data, rxAddress);
}
}
resolve(null);
});
interface RTCIceCandidate {
candidate: string;
sdpMid: string;
sdpMLineIndex: number;
usernameFragment: string;
};
interface RTCSessionDescription {
sdp: string;
type: "offer" | "answer";
}
async function getSDP(): Promise<string> {
return await Deno.readTextFile("./output.sdp");
}
async function getFingerprint(): Promise<string> {
const cert = new Uint8Array(32);
crypto.getRandomValues(cert);
const hash = encodeHex(await crypto.subtle.digest("SHA-256", cert)).toUpperCase();
return hash.split(/(..)/g).filter((s: string) => s !== "").join(":");
}
async function modifySDP(sdp: string): Promise<string> {
//let sdpObj = sdpTransform.parse(sdp);
const localAddress = "127.0.0.1";
let sdpObj: sdpTransform.SessionDescription = {
fingerprint: {
"type": "SHA-256",
"hash": await getFingerprint()
},
icePwd : "AAAABBBB",
iceUfrag: "AAAABBBB",
origin: {
username: "-",
sessionId: (Math.random()*10000000).toFixed(0),
sessionVersion: 0,
netType: "IN",
ipVer: 4,
address: localAddress,
},
timing: {
start: 0,
stop: 0
},
media: [
{
type: "video",
port: 1235,
protocol: "RTP/AVP",
direction: "sendrecv",
connection: {
ip: localAddress,
version: 4,
},
payloads: "126",
fmtp: [
],
rtp: [
{
payload: 126,
codec: "H264",
rate: 90000
}
]
}
]
};
console.log(sdpObj);
return sdpTransform.write(sdpObj);
}
router.post("/webrtc", async (ctx) => {
const body: {
ice: RTCIceCandidate[];
offer: RTCSessionDescription
} = await ctx.request.body.json();
const ice: RTCIceCandidate[] = [];
const answer: RTCSessionDescription = {
sdp: await modifySDP(await getSDP()),
type: "answer"
};
console.log(body.offer);
const remoteIce = body.ice.map((candidate: RTCIceCandidate) => parseIceCandidate(candidate.candidate))
.filter((c: IceCandidate | null) => c !== null)
.filter((c: IceCandidate | null) => c?.transport === "UDP" && c?.candidateType === "host")
//console.log(remoteIce);
if (body) {
ctx.response.status = 200;
ctx.response.body = {
ice: ice,
answer: answer
};
} else {
ctx.response.status = 400;
ctx.response.body = { error: "Invalid JSON" };
}
});

View File

@@ -0,0 +1,21 @@
{
"name": "webrtc-test",
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"typescript": "^5.0.0",
"vite": "^5.0.3"
}
}

13
gobot-gui/webrtc_test/src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
// See https://svelte.dev/docs/kit/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

View File

@@ -0,0 +1,2 @@
<h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>

View File

@@ -0,0 +1,92 @@
<script lang="ts">
import { onMount } from "svelte";
let state = "IDLE";
let localStream: MediaStream;
let videoElement: HTMLVideoElement;
let pc: RTCPeerConnection;
async function start() {
state = "CONNECTING";
pc = new RTCPeerConnection({
iceServers: [
{urls: "stun:stun.l.google.com:19302"},
],
// @ts-ignore
rtcpMuxPolicy: "negotiate"
});
const offer = await pc.createOffer({
offerToReceiveVideo: true,
});
let iceCandidates: RTCIceCandidate[] = [];
pc.addEventListener("icecandidate", (event) => {
if (event.candidate) {
iceCandidates.push(event.candidate);
}
});
await pc.setLocalDescription(offer);
state = "GATHERING ICE";
await new Promise((resolve) => {
function onIce() {
if (pc.iceGatheringState === "complete") {
pc.removeEventListener("icegatheringstatechange", onIce);
resolve(null);
}
}
if(pc.iceGatheringState === "complete") {2
resolve(null);
} else {
pc.addEventListener("icegatheringstatechange", onIce);
}
});
localStream = new MediaStream();
videoElement.srcObject = localStream;
pc.addEventListener("track", (ev) => {
console.log("TRACK!");
localStream.addTrack(ev.track);
});
state = "WAITING FOR ANSWER";
const data = await (await fetch("/api/webrtc", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
offer: offer,
ice: iceCandidates,
}),
})).json();
let answer: RTCSessionDescription = data.answer;
let ice: RTCIceCandidate[] = data.ice;
console.log(answer)
state = "GOT ANSWER!";
await pc.setRemoteDescription(answer);
console.log(ice);
ice.forEach((candidate) => {
pc.addIceCandidate(candidate);
});
}
</script>
<main>
<video bind:this={videoElement} autoplay playsinline></video>
<button on:click={start}>Connect!</button>
<p>{state}</p>
</main>

View File

@@ -0,0 +1,98 @@
<script lang="ts">
import { onMount } from "svelte";
let state = "IDLE";
let localStream: MediaStream;
let videoElement: HTMLVideoElement;
let pc: RTCPeerConnection;
async function start() {
state = "WAITING FOR OFFER";
let offer: RTCSessionDescription = null;
let offerIceCandidates: RTCIceCandidate[] = [];
await new Promise(async (resolve) => {
while(true) {
const res = await (fetch("/api/offer"));
if(res.status === 200) {
const data = await res.json();
offer = data.offer;
offerIceCandidates = data.ice;
resolve(null);
break;
}
await new Promise((r) => setTimeout(() => r(null), 250));
}
})
pc = new RTCPeerConnection({
iceServers: []
});
console.log(offerIceCandidates);
state = "SET TRACKS";
localStream = new MediaStream();
let camStream = await navigator.mediaDevices.getUserMedia({video: true});
camStream.getTracks().forEach((track) => {
pc.addTrack(track, camStream);
localStream.addTrack(track);
});
videoElement.srcObject = localStream;
state = "GATHERING ICE";
let iceCandidates: RTCIceCandidate[] = [];
pc.addEventListener("icecandidate", (event) => {
if (event.candidate) {
iceCandidates.push(event.candidate);
}
});
await pc.setRemoteDescription(offer);
offerIceCandidates.forEach((candidate) => pc.addIceCandidate(candidate));
let answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
await new Promise((resolve) => {
function onIce() {
if (pc.iceGatheringState === "complete") {
pc.removeEventListener("icegatheringstatechange", onIce);
resolve(null);
}
}
if(pc.iceGatheringState === "complete") {
resolve(null);
} else {
pc.addEventListener("icegatheringstatechange", onIce);
}
});
state = "SENDING ANSWER";
await fetch("/api/answer", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
answer: answer,
ice: iceCandidates,
}),
});
}
</script>
<main>
<video bind:this={videoElement} autoplay playsinline></video>
<button on:click={start}>Connect!</button>
<p>{state}</p>
</main>

BIN
gobot-gui/webrtc_test/static/favicon.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -0,0 +1,18 @@
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://svelte.dev/docs/kit/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
adapter: adapter()
}
};
export default config;

View File

@@ -0,0 +1,19 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

View File

@@ -0,0 +1,12 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()],
server: {
host: '0.0.0.0',
proxy: {
'/api': 'http://localhost:8080'
}
}
});

View File

@@ -0,0 +1,596 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@ampproject/remapping@^2.3.0":
version "2.3.0"
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4"
integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==
dependencies:
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.24"
"@esbuild/aix-ppc64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f"
integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==
"@esbuild/android-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052"
integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==
"@esbuild/android-arm@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28"
integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==
"@esbuild/android-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e"
integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==
"@esbuild/darwin-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a"
integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==
"@esbuild/darwin-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22"
integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==
"@esbuild/freebsd-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e"
integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==
"@esbuild/freebsd-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261"
integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==
"@esbuild/linux-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b"
integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==
"@esbuild/linux-arm@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9"
integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==
"@esbuild/linux-ia32@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2"
integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==
"@esbuild/linux-loong64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df"
integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==
"@esbuild/linux-mips64el@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe"
integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==
"@esbuild/linux-ppc64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4"
integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==
"@esbuild/linux-riscv64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc"
integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==
"@esbuild/linux-s390x@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de"
integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==
"@esbuild/linux-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0"
integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==
"@esbuild/netbsd-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047"
integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==
"@esbuild/openbsd-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70"
integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==
"@esbuild/sunos-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b"
integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==
"@esbuild/win32-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d"
integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==
"@esbuild/win32-ia32@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b"
integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==
"@esbuild/win32-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c"
integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==
"@jridgewell/gen-mapping@^0.3.5":
version "0.3.5"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36"
integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==
dependencies:
"@jridgewell/set-array" "^1.2.1"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.24"
"@jridgewell/resolve-uri@^3.1.0":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
"@jridgewell/set-array@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280"
integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15", "@jridgewell/sourcemap-codec@^1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a"
integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
version "0.3.25"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0"
integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==
dependencies:
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@polka/url@^1.0.0-next.24":
version "1.0.0-next.28"
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.28.tgz#d45e01c4a56f143ee69c54dd6b12eade9e270a73"
integrity sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==
"@rollup/rollup-android-arm-eabi@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz#1661ff5ea9beb362795304cb916049aba7ac9c54"
integrity sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==
"@rollup/rollup-android-arm64@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz#2ffaa91f1b55a0082b8a722525741aadcbd3971e"
integrity sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==
"@rollup/rollup-darwin-arm64@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz#627007221b24b8cc3063703eee0b9177edf49c1f"
integrity sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==
"@rollup/rollup-darwin-x64@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz#0605506142b9e796c370d59c5984ae95b9758724"
integrity sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==
"@rollup/rollup-linux-arm-gnueabihf@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz#62dfd196d4b10c0c2db833897164d2d319ee0cbb"
integrity sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==
"@rollup/rollup-linux-arm-musleabihf@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz#53ce72aeb982f1f34b58b380baafaf6a240fddb3"
integrity sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==
"@rollup/rollup-linux-arm64-gnu@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz#1632990f62a75c74f43e4b14ab3597d7ed416496"
integrity sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==
"@rollup/rollup-linux-arm64-musl@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz#8c03a996efb41e257b414b2e0560b7a21f2d9065"
integrity sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==
"@rollup/rollup-linux-powerpc64le-gnu@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz#5b98729628d5bcc8f7f37b58b04d6845f85c7b5d"
integrity sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==
"@rollup/rollup-linux-riscv64-gnu@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz#48e42e41f4cabf3573cfefcb448599c512e22983"
integrity sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==
"@rollup/rollup-linux-s390x-gnu@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz#e0b4f9a966872cb7d3e21b9e412a4b7efd7f0b58"
integrity sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==
"@rollup/rollup-linux-x64-gnu@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz#78144741993100f47bd3da72fce215e077ae036b"
integrity sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==
"@rollup/rollup-linux-x64-musl@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz#d9fe32971883cd1bd858336bd33a1c3ca6146127"
integrity sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==
"@rollup/rollup-win32-arm64-msvc@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz#71fa3ea369316db703a909c790743972e98afae5"
integrity sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==
"@rollup/rollup-win32-ia32-msvc@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz#653f5989a60658e17d7576a3996deb3902e342e2"
integrity sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==
"@rollup/rollup-win32-x64-msvc@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz#0574d7e87b44ee8511d08cc7f914bcb802b70818"
integrity sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==
"@sveltejs/adapter-auto@^3.0.0":
version "3.3.0"
resolved "https://registry.yarnpkg.com/@sveltejs/adapter-auto/-/adapter-auto-3.3.0.tgz#34ebe894616a53c4bcee4a5e6cc0d8dd5acdeade"
integrity sha512-EJZqY7eMM+bdbR898Xt9ufawUHLPJu7w3wPr4Cc+T1iIDf3fufVLWg4C71OluIqsdJqv85E4biKuHo3XXIY0PQ==
dependencies:
import-meta-resolve "^4.1.0"
"@sveltejs/kit@^2.0.0":
version "2.7.2"
resolved "https://registry.yarnpkg.com/@sveltejs/kit/-/kit-2.7.2.tgz#9d799082a466fde4e227cd5bff2193907890f798"
integrity sha512-bFwrl+0bNr0/DHQZM0INwwSPNYqDjfsKRhUoa6rj9d8tDZzszBrJ3La6/HVFxWGONEigtG+SzHXa1BEa1BLdwA==
dependencies:
"@types/cookie" "^0.6.0"
cookie "^0.6.0"
devalue "^5.1.0"
esm-env "^1.0.0"
import-meta-resolve "^4.1.0"
kleur "^4.1.5"
magic-string "^0.30.5"
mrmime "^2.0.0"
sade "^1.8.1"
set-cookie-parser "^2.6.0"
sirv "^3.0.0"
tiny-glob "^0.2.9"
"@sveltejs/vite-plugin-svelte-inspector@^3.0.0-next.0||^3.0.0":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-3.0.1.tgz#006bcab6ea90e09c65459133d4e3eaa6b1e83e28"
integrity sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==
dependencies:
debug "^4.3.7"
"@sveltejs/vite-plugin-svelte@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-4.0.0.tgz#4e7c2fe6fd262f6bbd7dc82085a76654cbaeafe5"
integrity sha512-kpVJwF+gNiMEsoHaw+FJL76IYiwBikkxYU83+BpqQLdVMff19KeRKLd2wisS8niNBMJ2omv5gG+iGDDwd8jzag==
dependencies:
"@sveltejs/vite-plugin-svelte-inspector" "^3.0.0-next.0||^3.0.0"
debug "^4.3.7"
deepmerge "^4.3.1"
kleur "^4.1.5"
magic-string "^0.30.12"
vitefu "^1.0.3"
"@types/cookie@^0.6.0":
version "0.6.0"
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5"
integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==
"@types/estree@*", "@types/estree@1.0.6", "@types/estree@^1.0.1", "@types/estree@^1.0.5":
version "1.0.6"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
acorn-typescript@^1.4.13:
version "1.4.13"
resolved "https://registry.yarnpkg.com/acorn-typescript/-/acorn-typescript-1.4.13.tgz#5f851c8bdda0aa716ffdd5f6ac084df8acc6f5ea"
integrity sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==
acorn@^8.12.1:
version "8.13.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.13.0.tgz#2a30d670818ad16ddd6a35d3842dacec9e5d7ca3"
integrity sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==
aria-query@^5.3.1:
version "5.3.2"
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59"
integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==
axobject-query@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee"
integrity sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==
chokidar@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.1.tgz#4a6dff66798fb0f72a94f616abbd7e1a19f31d41"
integrity sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==
dependencies:
readdirp "^4.0.1"
cookie@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
debug@^4.3.7:
version "4.3.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
dependencies:
ms "^2.1.3"
deepmerge@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a"
integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
devalue@^5.1.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/devalue/-/devalue-5.1.1.tgz#a71887ac0f354652851752654e4bd435a53891ae"
integrity sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==
esbuild@^0.21.3:
version "0.21.5"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d"
integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==
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"
esm-env@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/esm-env/-/esm-env-1.0.0.tgz#b124b40b180711690a4cb9b00d16573391950413"
integrity sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==
esrap@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/esrap/-/esrap-1.2.2.tgz#b9e3afee3f12238563a763b7fa86220de2c53203"
integrity sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==
dependencies:
"@jridgewell/sourcemap-codec" "^1.4.15"
"@types/estree" "^1.0.1"
fdir@^6.2.0:
version "6.4.2"
resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.2.tgz#ddaa7ce1831b161bc3657bb99cb36e1622702689"
integrity sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==
fsevents@~2.3.2, fsevents@~2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
globalyzer@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/globalyzer/-/globalyzer-0.1.0.tgz#cb76da79555669a1519d5a8edf093afaa0bf1465"
integrity sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==
globrex@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098"
integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==
import-meta-resolve@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz#f9db8bead9fafa61adb811db77a2bf22c5399706"
integrity sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==
is-reference@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-3.0.2.tgz#154747a01f45cd962404ee89d43837af2cba247c"
integrity sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==
dependencies:
"@types/estree" "*"
kleur@^4.1.5:
version "4.1.5"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==
locate-character@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/locate-character/-/locate-character-3.0.0.tgz#0305c5b8744f61028ef5d01f444009e00779f974"
integrity sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==
magic-string@^0.30.11, magic-string@^0.30.12, magic-string@^0.30.5:
version "0.30.12"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.12.tgz#9eb11c9d072b9bcb4940a5b2c2e1a217e4ee1a60"
integrity sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==
dependencies:
"@jridgewell/sourcemap-codec" "^1.5.0"
mri@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==
mrmime@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-2.0.0.tgz#151082a6e06e59a9a39b46b3e14d5cfe92b3abb4"
integrity sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==
ms@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
nanoid@^3.3.7:
version "3.3.7"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
picocolors@^1.0.0, picocolors@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
postcss@^8.4.43:
version "8.4.47"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365"
integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==
dependencies:
nanoid "^3.3.7"
picocolors "^1.1.0"
source-map-js "^1.2.1"
readdirp@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.0.2.tgz#388fccb8b75665da3abffe2d8f8ed59fe74c230a"
integrity sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==
rollup@^4.20.0:
version "4.24.0"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.24.0.tgz#c14a3576f20622ea6a5c9cad7caca5e6e9555d05"
integrity sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==
dependencies:
"@types/estree" "1.0.6"
optionalDependencies:
"@rollup/rollup-android-arm-eabi" "4.24.0"
"@rollup/rollup-android-arm64" "4.24.0"
"@rollup/rollup-darwin-arm64" "4.24.0"
"@rollup/rollup-darwin-x64" "4.24.0"
"@rollup/rollup-linux-arm-gnueabihf" "4.24.0"
"@rollup/rollup-linux-arm-musleabihf" "4.24.0"
"@rollup/rollup-linux-arm64-gnu" "4.24.0"
"@rollup/rollup-linux-arm64-musl" "4.24.0"
"@rollup/rollup-linux-powerpc64le-gnu" "4.24.0"
"@rollup/rollup-linux-riscv64-gnu" "4.24.0"
"@rollup/rollup-linux-s390x-gnu" "4.24.0"
"@rollup/rollup-linux-x64-gnu" "4.24.0"
"@rollup/rollup-linux-x64-musl" "4.24.0"
"@rollup/rollup-win32-arm64-msvc" "4.24.0"
"@rollup/rollup-win32-ia32-msvc" "4.24.0"
"@rollup/rollup-win32-x64-msvc" "4.24.0"
fsevents "~2.3.2"
sade@^1.7.4, sade@^1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701"
integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==
dependencies:
mri "^1.1.0"
set-cookie-parser@^2.6.0:
version "2.7.1"
resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz#3016f150072202dfbe90fadee053573cc89d2943"
integrity sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==
sirv@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/sirv/-/sirv-3.0.0.tgz#f8d90fc528f65dff04cb597a88609d4e8a4361ce"
integrity sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==
dependencies:
"@polka/url" "^1.0.0-next.24"
mrmime "^2.0.0"
totalist "^3.0.0"
source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
svelte-check@^4.0.0:
version "4.0.5"
resolved "https://registry.yarnpkg.com/svelte-check/-/svelte-check-4.0.5.tgz#5cd910c3b1d50f38159c17cc3bae127cbbb55c8d"
integrity sha512-icBTBZ3ibBaywbXUat3cK6hB5Du+Kq9Z8CRuyLmm64XIe2/r+lQcbuBx/IQgsbrC+kT2jQ0weVpZSSRIPwB6jQ==
dependencies:
"@jridgewell/trace-mapping" "^0.3.25"
chokidar "^4.0.1"
fdir "^6.2.0"
picocolors "^1.0.0"
sade "^1.7.4"
svelte@^5.0.0:
version "5.0.5"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-5.0.5.tgz#0e29f0510822f137e7fba245a2d40abe798fc91b"
integrity sha512-f4WBlP5g8W6pEoDfx741lewMlemy+LIGpEqjGPWqnHVP92wqlQXl87U5O5Bi2tkSUrO95OxOoqwU8qlqiHmFKA==
dependencies:
"@ampproject/remapping" "^2.3.0"
"@jridgewell/sourcemap-codec" "^1.5.0"
"@types/estree" "^1.0.5"
acorn "^8.12.1"
acorn-typescript "^1.4.13"
aria-query "^5.3.1"
axobject-query "^4.1.0"
esm-env "^1.0.0"
esrap "^1.2.2"
is-reference "^3.0.2"
locate-character "^3.0.0"
magic-string "^0.30.11"
zimmerframe "^1.1.2"
tiny-glob@^0.2.9:
version "0.2.9"
resolved "https://registry.yarnpkg.com/tiny-glob/-/tiny-glob-0.2.9.tgz#2212d441ac17928033b110f8b3640683129d31e2"
integrity sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==
dependencies:
globalyzer "0.1.0"
globrex "^0.1.2"
totalist@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8"
integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==
typescript@^5.0.0:
version "5.6.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.3.tgz#5f3449e31c9d94febb17de03cc081dd56d81db5b"
integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==
vite@^5.0.3:
version "5.4.10"
resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.10.tgz#d358a7bd8beda6cf0f3b7a450a8c7693a4f80c18"
integrity sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==
dependencies:
esbuild "^0.21.3"
postcss "^8.4.43"
rollup "^4.20.0"
optionalDependencies:
fsevents "~2.3.3"
vitefu@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/vitefu/-/vitefu-1.0.3.tgz#0467c75ee2be951c35246605b7fdbdbfd03b65d1"
integrity sha512-iKKfOMBHob2WxEJbqbJjHAkmYgvFDPhuqrO82om83S8RLk+17FtyMBfcyeH8GqD0ihShtkMW/zzJgiA51hCNCQ==
zimmerframe@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/zimmerframe/-/zimmerframe-1.1.2.tgz#5b75f1fa83b07ae2a428d51e50f58e2ae6855e5e"
integrity sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==