Compare commits

..
20 Commits
Author SHA1 Message Date
jimmy 82c4b637bb Fix licensing. 2026-09-16 20:18:06 -04:00
jimmy ea7eb45665 Get identity and version number. 2026-09-16 19:49:38 -04:00
jimmy 11e799207e Support multiple videos. 2026-09-12 13:17:31 +00:00
jimmy 24281cd491 Refactor query pattern. 2026-08-27 09:38:49 -04:00
jimmy c59248f7a5 Move login page to server. 2026-08-26 12:06:59 -04:00
jimmy a34613a64a Support video login. 2026-08-26 14:16:18 +00:00
jimmy 207476efdc Don't show Appnav on login page. 2026-08-22 15:41:16 -04:00
jimmy 855c479caf First pass login support. 2026-08-22 10:42:56 -04:00
jimmy b46806ada1 General cleanup and refactoring. 2026-08-18 21:06:14 -04:00
jimmy 1e16d0e482 First pass diagnostics display. 2026-08-16 20:51:31 -04:00
jimmy 9d3767d68c Add socketio. 2026-08-16 16:32:23 -04:00
jimmy 4d2ba762f3 Init diagnostics. 2026-08-14 17:50:07 -04:00
jimmy 6d32d2d005 Move components to its own folder. 2026-08-14 14:14:53 -04:00
jimmy 5c37234bf6 Lyrical 2026-06-12 22:05:50 -04:00
jimmy 4336cc2cbc Don't hard code localhost for mediamtx location. 2026-06-03 15:23:35 -04:00
jimmy 3a2589d6aa Stream in costmap from mediamtx. 2026-05-26 15:38:15 -04:00
jimmy 4408a1359a Remove whoami, I don't know why I had that... 2026-05-26 15:18:10 -04:00
jimmy bfa963e912 It's ok to clear the outdir. 2026-03-08 09:23:49 -04:00
jimmy 9f74311128 Add react compiler. Clean out README. 2026-03-08 09:20:47 -04:00
jimmy f5d841f03b LICENSE 2026-03-08 09:06:29 -04:00
30 changed files with 1926 additions and 263 deletions

No files matched your search

Generated
+174 -20
View File
@@ -37,6 +37,7 @@ const RAW_RUNTIME_STATE =
["@types/react-dom", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:19.2.3"],\ ["@types/react-dom", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:19.2.3"],\
["@vitejs/plugin-react", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:5.1.4"],\ ["@vitejs/plugin-react", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:5.1.4"],\
["am_i_up_ui", "workspace:."],\ ["am_i_up_ui", "workspace:."],\
["babel-plugin-react-compiler", "npm:1.0.0"],\
["bootstrap", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:5.3.8"],\ ["bootstrap", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:5.3.8"],\
["eslint", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:9.39.3"],\ ["eslint", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:9.39.3"],\
["eslint-plugin-react-hooks", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:7.0.1"],\ ["eslint-plugin-react-hooks", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:7.0.1"],\
@@ -44,11 +45,14 @@ const RAW_RUNTIME_STATE =
["globals", "npm:16.5.0"],\ ["globals", "npm:16.5.0"],\
["jotai", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:2.18.0"],\ ["jotai", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:2.18.0"],\
["jotai-tanstack-query", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:0.11.0"],\ ["jotai-tanstack-query", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:0.11.0"],\
["mediamtx-webrtc-react", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:1.0.1"],\
["prettier", "npm:3.8.1"],\ ["prettier", "npm:3.8.1"],\
["react", "npm:19.2.4"],\ ["react", "npm:19.2.4"],\
["react-bootstrap", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:2.10.10"],\ ["react-bootstrap", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:2.10.10"],\
["react-bootstrap-icons", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:1.11.6"],\
["react-dom", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:19.2.4"],\ ["react-dom", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:19.2.4"],\
["react-router-dom", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:7.13.1"],\ ["react-router-dom", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:7.13.1"],\
["socket.io-client", "npm:4.8.3"],\
["typescript", "patch:typescript@npm%3A5.9.3#optional!builtin<compat/typescript>::version=5.9.3&hash=5786d5"],\ ["typescript", "patch:typescript@npm%3A5.9.3#optional!builtin<compat/typescript>::version=5.9.3&hash=5786d5"],\
["typescript-eslint", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:8.56.1"],\ ["typescript-eslint", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:8.56.1"],\
["vite", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:7.3.1"],\ ["vite", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:7.3.1"],\
@@ -94,7 +98,7 @@ const RAW_RUNTIME_STATE =
["@babel/types", "npm:7.29.0"],\ ["@babel/types", "npm:7.29.0"],\
["@jridgewell/remapping", "npm:2.3.5"],\ ["@jridgewell/remapping", "npm:2.3.5"],\
["convert-source-map", "npm:2.0.0"],\ ["convert-source-map", "npm:2.0.0"],\
["debug", "virtual:85d5d916b6a745b2f8de0d4b1704b7084bdd4f7573c1ef5d1c877f2c866045a9b29fe2bc752d1b73e531c378a0518dd3f9fa187b31427fb9bacbe34b14715dde#npm:4.4.3"],\ ["debug", "virtual:fd14f4b531a5a95a85dc33ff712d35b53c356fa12f6734083978806651903eae60196105e71a7d037cf3aeffe0d9c55861ee577971d38735aba6ad2f7a34a45a#npm:4.4.3"],\
["gensync", "npm:1.0.0-beta.2"],\ ["gensync", "npm:1.0.0-beta.2"],\
["json5", "npm:2.2.3"],\ ["json5", "npm:2.2.3"],\
["semver", "npm:6.3.1"]\ ["semver", "npm:6.3.1"]\
@@ -310,7 +314,7 @@ const RAW_RUNTIME_STATE =
["@babel/template", "npm:7.28.6"],\ ["@babel/template", "npm:7.28.6"],\
["@babel/traverse", "npm:7.29.0"],\ ["@babel/traverse", "npm:7.29.0"],\
["@babel/types", "npm:7.29.0"],\ ["@babel/types", "npm:7.29.0"],\
["debug", "virtual:85d5d916b6a745b2f8de0d4b1704b7084bdd4f7573c1ef5d1c877f2c866045a9b29fe2bc752d1b73e531c378a0518dd3f9fa187b31427fb9bacbe34b14715dde#npm:4.4.3"]\ ["debug", "virtual:fd14f4b531a5a95a85dc33ff712d35b53c356fa12f6734083978806651903eae60196105e71a7d037cf3aeffe0d9c55861ee577971d38735aba6ad2f7a34a45a#npm:4.4.3"]\
],\ ],\
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
@@ -598,7 +602,7 @@ const RAW_RUNTIME_STATE =
"packageDependencies": [\ "packageDependencies": [\
["@eslint/config-array", "npm:0.21.1"],\ ["@eslint/config-array", "npm:0.21.1"],\
["@eslint/object-schema", "npm:2.1.7"],\ ["@eslint/object-schema", "npm:2.1.7"],\
["debug", "virtual:85d5d916b6a745b2f8de0d4b1704b7084bdd4f7573c1ef5d1c877f2c866045a9b29fe2bc752d1b73e531c378a0518dd3f9fa187b31427fb9bacbe34b14715dde#npm:4.4.3"],\ ["debug", "virtual:fd14f4b531a5a95a85dc33ff712d35b53c356fa12f6734083978806651903eae60196105e71a7d037cf3aeffe0d9c55861ee577971d38735aba6ad2f7a34a45a#npm:4.4.3"],\
["minimatch", "npm:3.1.5"]\ ["minimatch", "npm:3.1.5"]\
],\ ],\
"linkType": "HARD"\ "linkType": "HARD"\
@@ -630,7 +634,7 @@ const RAW_RUNTIME_STATE =
"packageDependencies": [\ "packageDependencies": [\
["@eslint/eslintrc", "npm:3.3.4"],\ ["@eslint/eslintrc", "npm:3.3.4"],\
["ajv", "npm:6.14.0"],\ ["ajv", "npm:6.14.0"],\
["debug", "virtual:85d5d916b6a745b2f8de0d4b1704b7084bdd4f7573c1ef5d1c877f2c866045a9b29fe2bc752d1b73e531c378a0518dd3f9fa187b31427fb9bacbe34b14715dde#npm:4.4.3"],\ ["debug", "virtual:fd14f4b531a5a95a85dc33ff712d35b53c356fa12f6734083978806651903eae60196105e71a7d037cf3aeffe0d9c55861ee577971d38735aba6ad2f7a34a45a#npm:4.4.3"],\
["espree", "npm:10.4.0"],\ ["espree", "npm:10.4.0"],\
["globals", "npm:14.0.0"],\ ["globals", "npm:14.0.0"],\
["ignore", "npm:5.3.2"],\ ["ignore", "npm:5.3.2"],\
@@ -1149,6 +1153,15 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["@socket.io/component-emitter", [\
["npm:3.1.2", {\
"packageLocation": "./.yarn/cache/@socket.io-component-emitter-npm-3.1.2-bc4133d33f-c4242bad66.zip/node_modules/@socket.io/component-emitter/",\
"packageDependencies": [\
["@socket.io/component-emitter", "npm:3.1.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["@swc/helpers", [\ ["@swc/helpers", [\
["npm:0.5.19", {\ ["npm:0.5.19", {\
"packageLocation": "./.yarn/cache/@swc-helpers-npm-0.5.19-8b3f562b85-30317ee644.zip/node_modules/@swc/helpers/",\ "packageLocation": "./.yarn/cache/@swc-helpers-npm-0.5.19-8b3f562b85-30317ee644.zip/node_modules/@swc/helpers/",\
@@ -1375,7 +1388,7 @@ const RAW_RUNTIME_STATE =
["@typescript-eslint/types", "npm:8.56.1"],\ ["@typescript-eslint/types", "npm:8.56.1"],\
["@typescript-eslint/typescript-estree", "virtual:58ccd472628fcf79c5364cbafb0fa7401eab7f9f0a8d7e57daff8d16b3df3fcd9fae4f499821557ed433fdc33c9608f2e228cdb9e43ac4ccbde485d9fe0d92aa#npm:8.56.1"],\ ["@typescript-eslint/typescript-estree", "virtual:58ccd472628fcf79c5364cbafb0fa7401eab7f9f0a8d7e57daff8d16b3df3fcd9fae4f499821557ed433fdc33c9608f2e228cdb9e43ac4ccbde485d9fe0d92aa#npm:8.56.1"],\
["@typescript-eslint/visitor-keys", "npm:8.56.1"],\ ["@typescript-eslint/visitor-keys", "npm:8.56.1"],\
["debug", "virtual:85d5d916b6a745b2f8de0d4b1704b7084bdd4f7573c1ef5d1c877f2c866045a9b29fe2bc752d1b73e531c378a0518dd3f9fa187b31427fb9bacbe34b14715dde#npm:4.4.3"],\ ["debug", "virtual:fd14f4b531a5a95a85dc33ff712d35b53c356fa12f6734083978806651903eae60196105e71a7d037cf3aeffe0d9c55861ee577971d38735aba6ad2f7a34a45a#npm:4.4.3"],\
["eslint", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:9.39.3"],\ ["eslint", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:9.39.3"],\
["typescript", "patch:typescript@npm%3A5.9.3#optional!builtin<compat/typescript>::version=5.9.3&hash=5786d5"]\ ["typescript", "patch:typescript@npm%3A5.9.3#optional!builtin<compat/typescript>::version=5.9.3&hash=5786d5"]\
],\ ],\
@@ -1403,7 +1416,7 @@ const RAW_RUNTIME_STATE =
["@typescript-eslint/project-service", "virtual:08eece7f558a61e3ba42d291e347c98bcfb1ebfb15f338a48676e1eb239671d197c1afd15b648b5d4feafc689a0004679f0f549b21e3ba4e2b84a1430d054544#npm:8.56.1"],\ ["@typescript-eslint/project-service", "virtual:08eece7f558a61e3ba42d291e347c98bcfb1ebfb15f338a48676e1eb239671d197c1afd15b648b5d4feafc689a0004679f0f549b21e3ba4e2b84a1430d054544#npm:8.56.1"],\
["@typescript-eslint/tsconfig-utils", "virtual:08eece7f558a61e3ba42d291e347c98bcfb1ebfb15f338a48676e1eb239671d197c1afd15b648b5d4feafc689a0004679f0f549b21e3ba4e2b84a1430d054544#npm:8.56.1"],\ ["@typescript-eslint/tsconfig-utils", "virtual:08eece7f558a61e3ba42d291e347c98bcfb1ebfb15f338a48676e1eb239671d197c1afd15b648b5d4feafc689a0004679f0f549b21e3ba4e2b84a1430d054544#npm:8.56.1"],\
["@typescript-eslint/types", "npm:8.56.1"],\ ["@typescript-eslint/types", "npm:8.56.1"],\
["debug", "virtual:85d5d916b6a745b2f8de0d4b1704b7084bdd4f7573c1ef5d1c877f2c866045a9b29fe2bc752d1b73e531c378a0518dd3f9fa187b31427fb9bacbe34b14715dde#npm:4.4.3"],\ ["debug", "virtual:fd14f4b531a5a95a85dc33ff712d35b53c356fa12f6734083978806651903eae60196105e71a7d037cf3aeffe0d9c55861ee577971d38735aba6ad2f7a34a45a#npm:4.4.3"],\
["typescript", "patch:typescript@npm%3A5.9.3#optional!builtin<compat/typescript>::version=5.9.3&hash=5786d5"]\ ["typescript", "patch:typescript@npm%3A5.9.3#optional!builtin<compat/typescript>::version=5.9.3&hash=5786d5"]\
],\ ],\
"packagePeers": [\ "packagePeers": [\
@@ -1463,7 +1476,7 @@ const RAW_RUNTIME_STATE =
["@typescript-eslint/types", "npm:8.56.1"],\ ["@typescript-eslint/types", "npm:8.56.1"],\
["@typescript-eslint/typescript-estree", "virtual:58ccd472628fcf79c5364cbafb0fa7401eab7f9f0a8d7e57daff8d16b3df3fcd9fae4f499821557ed433fdc33c9608f2e228cdb9e43ac4ccbde485d9fe0d92aa#npm:8.56.1"],\ ["@typescript-eslint/typescript-estree", "virtual:58ccd472628fcf79c5364cbafb0fa7401eab7f9f0a8d7e57daff8d16b3df3fcd9fae4f499821557ed433fdc33c9608f2e228cdb9e43ac4ccbde485d9fe0d92aa#npm:8.56.1"],\
["@typescript-eslint/utils", "virtual:58ccd472628fcf79c5364cbafb0fa7401eab7f9f0a8d7e57daff8d16b3df3fcd9fae4f499821557ed433fdc33c9608f2e228cdb9e43ac4ccbde485d9fe0d92aa#npm:8.56.1"],\ ["@typescript-eslint/utils", "virtual:58ccd472628fcf79c5364cbafb0fa7401eab7f9f0a8d7e57daff8d16b3df3fcd9fae4f499821557ed433fdc33c9608f2e228cdb9e43ac4ccbde485d9fe0d92aa#npm:8.56.1"],\
["debug", "virtual:85d5d916b6a745b2f8de0d4b1704b7084bdd4f7573c1ef5d1c877f2c866045a9b29fe2bc752d1b73e531c378a0518dd3f9fa187b31427fb9bacbe34b14715dde#npm:4.4.3"],\ ["debug", "virtual:fd14f4b531a5a95a85dc33ff712d35b53c356fa12f6734083978806651903eae60196105e71a7d037cf3aeffe0d9c55861ee577971d38735aba6ad2f7a34a45a#npm:4.4.3"],\
["eslint", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:9.39.3"],\ ["eslint", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:9.39.3"],\
["ts-api-utils", "virtual:79e3fd711d219a7e195f7717b302bba495278e314f4cbf21fc912bccc21ee612c45d3bed436698ce744dcc117f9112db1d68749c33956122d7e1360e6bcb3f62#npm:2.4.0"],\ ["ts-api-utils", "virtual:79e3fd711d219a7e195f7717b302bba495278e314f4cbf21fc912bccc21ee612c45d3bed436698ce744dcc117f9112db1d68749c33956122d7e1360e6bcb3f62#npm:2.4.0"],\
["typescript", "patch:typescript@npm%3A5.9.3#optional!builtin<compat/typescript>::version=5.9.3&hash=5786d5"]\ ["typescript", "patch:typescript@npm%3A5.9.3#optional!builtin<compat/typescript>::version=5.9.3&hash=5786d5"]\
@@ -1503,7 +1516,7 @@ const RAW_RUNTIME_STATE =
["@typescript-eslint/types", "npm:8.56.1"],\ ["@typescript-eslint/types", "npm:8.56.1"],\
["@typescript-eslint/typescript-estree", "virtual:58ccd472628fcf79c5364cbafb0fa7401eab7f9f0a8d7e57daff8d16b3df3fcd9fae4f499821557ed433fdc33c9608f2e228cdb9e43ac4ccbde485d9fe0d92aa#npm:8.56.1"],\ ["@typescript-eslint/typescript-estree", "virtual:58ccd472628fcf79c5364cbafb0fa7401eab7f9f0a8d7e57daff8d16b3df3fcd9fae4f499821557ed433fdc33c9608f2e228cdb9e43ac4ccbde485d9fe0d92aa#npm:8.56.1"],\
["@typescript-eslint/visitor-keys", "npm:8.56.1"],\ ["@typescript-eslint/visitor-keys", "npm:8.56.1"],\
["debug", "virtual:85d5d916b6a745b2f8de0d4b1704b7084bdd4f7573c1ef5d1c877f2c866045a9b29fe2bc752d1b73e531c378a0518dd3f9fa187b31427fb9bacbe34b14715dde#npm:4.4.3"],\ ["debug", "virtual:fd14f4b531a5a95a85dc33ff712d35b53c356fa12f6734083978806651903eae60196105e71a7d037cf3aeffe0d9c55861ee577971d38735aba6ad2f7a34a45a#npm:4.4.3"],\
["minimatch", "npm:10.2.4"],\ ["minimatch", "npm:10.2.4"],\
["semver", "npm:7.7.4"],\ ["semver", "npm:7.7.4"],\
["tinyglobby", "npm:0.2.15"],\ ["tinyglobby", "npm:0.2.15"],\
@@ -1661,6 +1674,7 @@ const RAW_RUNTIME_STATE =
["@types/react-dom", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:19.2.3"],\ ["@types/react-dom", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:19.2.3"],\
["@vitejs/plugin-react", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:5.1.4"],\ ["@vitejs/plugin-react", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:5.1.4"],\
["am_i_up_ui", "workspace:."],\ ["am_i_up_ui", "workspace:."],\
["babel-plugin-react-compiler", "npm:1.0.0"],\
["bootstrap", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:5.3.8"],\ ["bootstrap", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:5.3.8"],\
["eslint", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:9.39.3"],\ ["eslint", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:9.39.3"],\
["eslint-plugin-react-hooks", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:7.0.1"],\ ["eslint-plugin-react-hooks", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:7.0.1"],\
@@ -1668,11 +1682,14 @@ const RAW_RUNTIME_STATE =
["globals", "npm:16.5.0"],\ ["globals", "npm:16.5.0"],\
["jotai", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:2.18.0"],\ ["jotai", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:2.18.0"],\
["jotai-tanstack-query", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:0.11.0"],\ ["jotai-tanstack-query", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:0.11.0"],\
["mediamtx-webrtc-react", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:1.0.1"],\
["prettier", "npm:3.8.1"],\ ["prettier", "npm:3.8.1"],\
["react", "npm:19.2.4"],\ ["react", "npm:19.2.4"],\
["react-bootstrap", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:2.10.10"],\ ["react-bootstrap", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:2.10.10"],\
["react-bootstrap-icons", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:1.11.6"],\
["react-dom", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:19.2.4"],\ ["react-dom", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:19.2.4"],\
["react-router-dom", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:7.13.1"],\ ["react-router-dom", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:7.13.1"],\
["socket.io-client", "npm:4.8.3"],\
["typescript", "patch:typescript@npm%3A5.9.3#optional!builtin<compat/typescript>::version=5.9.3&hash=5786d5"],\ ["typescript", "patch:typescript@npm%3A5.9.3#optional!builtin<compat/typescript>::version=5.9.3&hash=5786d5"],\
["typescript-eslint", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:8.56.1"],\ ["typescript-eslint", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:8.56.1"],\
["vite", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:7.3.1"],\ ["vite", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:7.3.1"],\
@@ -1700,6 +1717,16 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["babel-plugin-react-compiler", [\
["npm:1.0.0", {\
"packageLocation": "./.yarn/cache/babel-plugin-react-compiler-npm-1.0.0-5beba4221c-9406267ada.zip/node_modules/babel-plugin-react-compiler/",\
"packageDependencies": [\
["@babel/types", "npm:7.29.0"],\
["babel-plugin-react-compiler", "npm:1.0.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["balanced-match", [\ ["balanced-match", [\
["npm:1.0.2", {\ ["npm:1.0.2", {\
"packageLocation": "./.yarn/cache/balanced-match-npm-1.0.2-a53c126459-9308baf0a7.zip/node_modules/balanced-match/",\ "packageLocation": "./.yarn/cache/balanced-match-npm-1.0.2-a53c126459-9308baf0a7.zip/node_modules/balanced-match/",\
@@ -1922,11 +1949,11 @@ const RAW_RUNTIME_STATE =
],\ ],\
"linkType": "SOFT"\ "linkType": "SOFT"\
}],\ }],\
["virtual:85d5d916b6a745b2f8de0d4b1704b7084bdd4f7573c1ef5d1c877f2c866045a9b29fe2bc752d1b73e531c378a0518dd3f9fa187b31427fb9bacbe34b14715dde#npm:4.4.3", {\ ["virtual:fd14f4b531a5a95a85dc33ff712d35b53c356fa12f6734083978806651903eae60196105e71a7d037cf3aeffe0d9c55861ee577971d38735aba6ad2f7a34a45a#npm:4.4.3", {\
"packageLocation": "./.yarn/__virtual__/debug-virtual-ce2b683fbc/0/cache/debug-npm-4.4.3-0105c6123a-d79136ec6c.zip/node_modules/debug/",\ "packageLocation": "./.yarn/__virtual__/debug-virtual-b7022d90de/0/cache/debug-npm-4.4.3-0105c6123a-d79136ec6c.zip/node_modules/debug/",\
"packageDependencies": [\ "packageDependencies": [\
["@types/supports-color", null],\ ["@types/supports-color", null],\
["debug", "virtual:85d5d916b6a745b2f8de0d4b1704b7084bdd4f7573c1ef5d1c877f2c866045a9b29fe2bc752d1b73e531c378a0518dd3f9fa187b31427fb9bacbe34b14715dde#npm:4.4.3"],\ ["debug", "virtual:fd14f4b531a5a95a85dc33ff712d35b53c356fa12f6734083978806651903eae60196105e71a7d037cf3aeffe0d9c55861ee577971d38735aba6ad2f7a34a45a#npm:4.4.3"],\
["ms", "npm:2.1.3"],\ ["ms", "npm:2.1.3"],\
["supports-color", null]\ ["supports-color", null]\
],\ ],\
@@ -1975,6 +2002,29 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["engine.io-client", [\
["npm:6.6.6", {\
"packageLocation": "./.yarn/cache/engine.io-client-npm-6.6.6-fd14f4b531-805995b57b.zip/node_modules/engine.io-client/",\
"packageDependencies": [\
["@socket.io/component-emitter", "npm:3.1.2"],\
["debug", "virtual:fd14f4b531a5a95a85dc33ff712d35b53c356fa12f6734083978806651903eae60196105e71a7d037cf3aeffe0d9c55861ee577971d38735aba6ad2f7a34a45a#npm:4.4.3"],\
["engine.io-client", "npm:6.6.6"],\
["engine.io-parser", "npm:5.2.3"],\
["ws", "virtual:fd14f4b531a5a95a85dc33ff712d35b53c356fa12f6734083978806651903eae60196105e71a7d037cf3aeffe0d9c55861ee577971d38735aba6ad2f7a34a45a#npm:8.21.3"],\
["xmlhttprequest-ssl", "npm:2.1.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["engine.io-parser", [\
["npm:5.2.3", {\
"packageLocation": "./.yarn/cache/engine.io-parser-npm-5.2.3-6dfa36cede-ed4900d8db.zip/node_modules/engine.io-parser/",\
"packageDependencies": [\
["engine.io-parser", "npm:5.2.3"]\
],\
"linkType": "HARD"\
}]\
]],\
["env-paths", [\ ["env-paths", [\
["npm:2.2.1", {\ ["npm:2.2.1", {\
"packageLocation": "./.yarn/cache/env-paths-npm-2.2.1-7c7577428c-285325677b.zip/node_modules/env-paths/",\ "packageLocation": "./.yarn/cache/env-paths-npm-2.2.1-7c7577428c-285325677b.zip/node_modules/env-paths/",\
@@ -2064,7 +2114,7 @@ const RAW_RUNTIME_STATE =
["ajv", "npm:6.14.0"],\ ["ajv", "npm:6.14.0"],\
["chalk", "npm:4.1.2"],\ ["chalk", "npm:4.1.2"],\
["cross-spawn", "npm:7.0.6"],\ ["cross-spawn", "npm:7.0.6"],\
["debug", "virtual:85d5d916b6a745b2f8de0d4b1704b7084bdd4f7573c1ef5d1c877f2c866045a9b29fe2bc752d1b73e531c378a0518dd3f9fa187b31427fb9bacbe34b14715dde#npm:4.4.3"],\ ["debug", "virtual:fd14f4b531a5a95a85dc33ff712d35b53c356fa12f6734083978806651903eae60196105e71a7d037cf3aeffe0d9c55861ee577971d38735aba6ad2f7a34a45a#npm:4.4.3"],\
["escape-string-regexp", "npm:4.0.0"],\ ["escape-string-regexp", "npm:4.0.0"],\
["eslint", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:9.39.3"],\ ["eslint", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:9.39.3"],\
["eslint-scope", "npm:8.4.0"],\ ["eslint-scope", "npm:8.4.0"],\
@@ -2443,7 +2493,7 @@ const RAW_RUNTIME_STATE =
"packageLocation": "./.yarn/cache/http-proxy-agent-npm-7.0.2-643ed7cc33-4207b06a45.zip/node_modules/http-proxy-agent/",\ "packageLocation": "./.yarn/cache/http-proxy-agent-npm-7.0.2-643ed7cc33-4207b06a45.zip/node_modules/http-proxy-agent/",\
"packageDependencies": [\ "packageDependencies": [\
["agent-base", "npm:7.1.4"],\ ["agent-base", "npm:7.1.4"],\
["debug", "virtual:85d5d916b6a745b2f8de0d4b1704b7084bdd4f7573c1ef5d1c877f2c866045a9b29fe2bc752d1b73e531c378a0518dd3f9fa187b31427fb9bacbe34b14715dde#npm:4.4.3"],\ ["debug", "virtual:fd14f4b531a5a95a85dc33ff712d35b53c356fa12f6734083978806651903eae60196105e71a7d037cf3aeffe0d9c55861ee577971d38735aba6ad2f7a34a45a#npm:4.4.3"],\
["http-proxy-agent", "npm:7.0.2"]\ ["http-proxy-agent", "npm:7.0.2"]\
],\ ],\
"linkType": "HARD"\ "linkType": "HARD"\
@@ -2454,7 +2504,7 @@ const RAW_RUNTIME_STATE =
"packageLocation": "./.yarn/cache/https-proxy-agent-npm-7.0.6-27a95c2690-f729219bc7.zip/node_modules/https-proxy-agent/",\ "packageLocation": "./.yarn/cache/https-proxy-agent-npm-7.0.6-27a95c2690-f729219bc7.zip/node_modules/https-proxy-agent/",\
"packageDependencies": [\ "packageDependencies": [\
["agent-base", "npm:7.1.4"],\ ["agent-base", "npm:7.1.4"],\
["debug", "virtual:85d5d916b6a745b2f8de0d4b1704b7084bdd4f7573c1ef5d1c877f2c866045a9b29fe2bc752d1b73e531c378a0518dd3f9fa187b31427fb9bacbe34b14715dde#npm:4.4.3"],\ ["debug", "virtual:fd14f4b531a5a95a85dc33ff712d35b53c356fa12f6734083978806651903eae60196105e71a7d037cf3aeffe0d9c55861ee577971d38735aba6ad2f7a34a45a#npm:4.4.3"],\
["https-proxy-agent", "npm:7.0.6"]\ ["https-proxy-agent", "npm:7.0.6"]\
],\ ],\
"linkType": "HARD"\ "linkType": "HARD"\
@@ -2775,6 +2825,28 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["mediamtx-webrtc-react", [\
["npm:1.0.1", {\
"packageLocation": "./.yarn/cache/mediamtx-webrtc-react-npm-1.0.1-de237e3035-ee1e630f20.zip/node_modules/mediamtx-webrtc-react/",\
"packageDependencies": [\
["mediamtx-webrtc-react", "npm:1.0.1"]\
],\
"linkType": "SOFT"\
}],\
["virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:1.0.1", {\
"packageLocation": "./.yarn/__virtual__/mediamtx-webrtc-react-virtual-8ea5a37835/0/cache/mediamtx-webrtc-react-npm-1.0.1-de237e3035-ee1e630f20.zip/node_modules/mediamtx-webrtc-react/",\
"packageDependencies": [\
["@types/react", "npm:19.2.14"],\
["mediamtx-webrtc-react", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:1.0.1"],\
["react", "npm:19.2.4"]\
],\
"packagePeers": [\
"@types/react",\
"react"\
],\
"linkType": "HARD"\
}]\
]],\
["minimatch", [\ ["minimatch", [\
["npm:10.2.4", {\ ["npm:10.2.4", {\
"packageLocation": "./.yarn/cache/minimatch-npm-10.2.4-11f0605299-35f3dfb7b9.zip/node_modules/minimatch/",\ "packageLocation": "./.yarn/cache/minimatch-npm-10.2.4-11f0605299-35f3dfb7b9.zip/node_modules/minimatch/",\
@@ -3189,6 +3261,29 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["react-bootstrap-icons", [\
["npm:1.11.6", {\
"packageLocation": "./.yarn/cache/react-bootstrap-icons-npm-1.11.6-2dbc39d6ac-4dfd75c980.zip/node_modules/react-bootstrap-icons/",\
"packageDependencies": [\
["react-bootstrap-icons", "npm:1.11.6"]\
],\
"linkType": "SOFT"\
}],\
["virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:1.11.6", {\
"packageLocation": "./.yarn/__virtual__/react-bootstrap-icons-virtual-ffeae68cb7/0/cache/react-bootstrap-icons-npm-1.11.6-2dbc39d6ac-4dfd75c980.zip/node_modules/react-bootstrap-icons/",\
"packageDependencies": [\
["@types/react", "npm:19.2.14"],\
["prop-types", "npm:15.8.1"],\
["react", "npm:19.2.4"],\
["react-bootstrap-icons", "virtual:ae939b5bf1c4da31987d1d461851d6cc956667f76e9d6d3a45643e06af20bf533b9bcec6bab70ed0ca795aebf9ce43b3ed74bd20004783b74b2a0cc963cd86b8#npm:1.11.6"]\
],\
"packagePeers": [\
"@types/react",\
"react"\
],\
"linkType": "HARD"\
}]\
]],\
["react-dom", [\ ["react-dom", [\
["npm:19.2.4", {\ ["npm:19.2.4", {\
"packageLocation": "./.yarn/cache/react-dom-npm-19.2.4-4921ae503e-f0c63f1794.zip/node_modules/react-dom/",\ "packageLocation": "./.yarn/cache/react-dom-npm-19.2.4-4921ae503e-f0c63f1794.zip/node_modules/react-dom/",\
@@ -3449,6 +3544,30 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["socket.io-client", [\
["npm:4.8.3", {\
"packageLocation": "./.yarn/cache/socket.io-client-npm-4.8.3-6800638250-76c0d86de0.zip/node_modules/socket.io-client/",\
"packageDependencies": [\
["@socket.io/component-emitter", "npm:3.1.2"],\
["debug", "virtual:fd14f4b531a5a95a85dc33ff712d35b53c356fa12f6734083978806651903eae60196105e71a7d037cf3aeffe0d9c55861ee577971d38735aba6ad2f7a34a45a#npm:4.4.3"],\
["engine.io-client", "npm:6.6.6"],\
["socket.io-client", "npm:4.8.3"],\
["socket.io-parser", "npm:4.2.7"]\
],\
"linkType": "HARD"\
}]\
]],\
["socket.io-parser", [\
["npm:4.2.7", {\
"packageLocation": "./.yarn/cache/socket.io-parser-npm-4.2.7-0fe17183fd-16a5579718.zip/node_modules/socket.io-parser/",\
"packageDependencies": [\
["@socket.io/component-emitter", "npm:3.1.2"],\
["debug", "virtual:fd14f4b531a5a95a85dc33ff712d35b53c356fa12f6734083978806651903eae60196105e71a7d037cf3aeffe0d9c55861ee577971d38735aba6ad2f7a34a45a#npm:4.4.3"],\
["socket.io-parser", "npm:4.2.7"]\
],\
"linkType": "HARD"\
}]\
]],\
["socks", [\ ["socks", [\
["npm:2.8.7", {\ ["npm:2.8.7", {\
"packageLocation": "./.yarn/cache/socks-npm-2.8.7-d1d20aae19-2805a43a1c.zip/node_modules/socks/",\ "packageLocation": "./.yarn/cache/socks-npm-2.8.7-d1d20aae19-2805a43a1c.zip/node_modules/socks/",\
@@ -3465,7 +3584,7 @@ const RAW_RUNTIME_STATE =
"packageLocation": "./.yarn/cache/socks-proxy-agent-npm-8.0.5-24d77a90dc-5d2c6cecba.zip/node_modules/socks-proxy-agent/",\ "packageLocation": "./.yarn/cache/socks-proxy-agent-npm-8.0.5-24d77a90dc-5d2c6cecba.zip/node_modules/socks-proxy-agent/",\
"packageDependencies": [\ "packageDependencies": [\
["agent-base", "npm:7.1.4"],\ ["agent-base", "npm:7.1.4"],\
["debug", "virtual:85d5d916b6a745b2f8de0d4b1704b7084bdd4f7573c1ef5d1c877f2c866045a9b29fe2bc752d1b73e531c378a0518dd3f9fa187b31427fb9bacbe34b14715dde#npm:4.4.3"],\ ["debug", "virtual:fd14f4b531a5a95a85dc33ff712d35b53c356fa12f6734083978806651903eae60196105e71a7d037cf3aeffe0d9c55861ee577971d38735aba6ad2f7a34a45a#npm:4.4.3"],\
["socks", "npm:2.8.7"],\ ["socks", "npm:2.8.7"],\
["socks-proxy-agent", "npm:8.0.5"]\ ["socks-proxy-agent", "npm:8.0.5"]\
],\ ],\
@@ -3826,6 +3945,41 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["ws", [\
["npm:8.21.3", {\
"packageLocation": "./.yarn/cache/ws-npm-8.21.3-63c47e1f60-7b28dc2863.zip/node_modules/ws/",\
"packageDependencies": [\
["ws", "npm:8.21.3"]\
],\
"linkType": "SOFT"\
}],\
["virtual:fd14f4b531a5a95a85dc33ff712d35b53c356fa12f6734083978806651903eae60196105e71a7d037cf3aeffe0d9c55861ee577971d38735aba6ad2f7a34a45a#npm:8.21.3", {\
"packageLocation": "./.yarn/__virtual__/ws-virtual-097af440a8/0/cache/ws-npm-8.21.3-63c47e1f60-7b28dc2863.zip/node_modules/ws/",\
"packageDependencies": [\
["@types/bufferutil", null],\
["@types/utf-8-validate", null],\
["bufferutil", null],\
["utf-8-validate", null],\
["ws", "virtual:fd14f4b531a5a95a85dc33ff712d35b53c356fa12f6734083978806651903eae60196105e71a7d037cf3aeffe0d9c55861ee577971d38735aba6ad2f7a34a45a#npm:8.21.3"]\
],\
"packagePeers": [\
"@types/bufferutil",\
"@types/utf-8-validate",\
"bufferutil",\
"utf-8-validate"\
],\
"linkType": "HARD"\
}]\
]],\
["xmlhttprequest-ssl", [\
["npm:2.1.2", {\
"packageLocation": "./.yarn/cache/xmlhttprequest-ssl-npm-2.1.2-7d8e3817bc-70d6086932.zip/node_modules/xmlhttprequest-ssl/",\
"packageDependencies": [\
["xmlhttprequest-ssl", "npm:2.1.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["yallist", [\ ["yallist", [\
["npm:3.1.1", {\ ["npm:3.1.1", {\
"packageLocation": "./.yarn/cache/yallist-npm-3.1.1-a568a556b4-c66a5c46bc.zip/node_modules/yallist/",\ "packageLocation": "./.yarn/cache/yallist-npm-3.1.1-a568a556b4-c66a5c46bc.zip/node_modules/yallist/",\
@@ -4483,7 +4637,7 @@ async function copyFileViaIndex(
const isIndexModified = indexStat?.mtimeMs !== defaultTimeMs; const isIndexModified = indexStat?.mtimeMs !== defaultTimeMs;
if (isDestinationHardlinkedFromIndex) { if (isDestinationHardlinkedFromIndex) {
if (isIndexModified && linkStrategy.autoRepair) { if (isIndexModified && linkStrategy.autoRepair) {
atomicBehavior = 0 /* Lock */; atomicBehavior = 0; /* Lock */
indexStat = null; indexStat = null;
} }
} }
@@ -4703,7 +4857,7 @@ class CustomStatWatcher extends events.EventEmitter {
fakeFs; fakeFs;
path; path;
bigint; bigint;
status = "ready" /* Ready */; status = "ready"; /* Ready */
changeListeners = /* @__PURE__ */ new Map(); changeListeners = /* @__PURE__ */ new Map();
lastStats; lastStats;
startTimeout = null; startTimeout = null;
@@ -4721,7 +4875,7 @@ class CustomStatWatcher extends events.EventEmitter {
} }
start() { start() {
assertStatus(this.status, "ready" /* Ready */); assertStatus(this.status, "ready" /* Ready */);
this.status = "running" /* Running */; this.status = "running"; /* Running */
this.startTimeout = setTimeout(() => { this.startTimeout = setTimeout(() => {
this.startTimeout = null; this.startTimeout = null;
if (!this.fakeFs.existsSync(this.path)) { if (!this.fakeFs.existsSync(this.path)) {
@@ -4731,7 +4885,7 @@ class CustomStatWatcher extends events.EventEmitter {
} }
stop() { stop() {
assertStatus(this.status, "running" /* Running */); assertStatus(this.status, "running" /* Running */);
this.status = "stopped" /* Stopped */; this.status = "stopped"; /* Stopped */
if (this.startTimeout !== null) { if (this.startTimeout !== null) {
clearTimeout(this.startTimeout); clearTimeout(this.startTimeout);
this.startTimeout = null; this.startTimeout = null;
@@ -8198,7 +8352,7 @@ var libzipSync = { exports: {} };
function UTF8ToString(ptr, maxBytesToRead) { function UTF8ToString(ptr, maxBytesToRead) {
if (!ptr) return ""; if (!ptr) return "";
var maxPtr = ptr + maxBytesToRead; var maxPtr = ptr + maxBytesToRead;
for (var end = ptr; !(end >= maxPtr) && HEAPU8[end]; ) ++end; for (var end = ptr; !(end >= maxPtr) && HEAPU8[end];) ++end;
return UTF8Decoder.decode(HEAPU8.subarray(ptr, end)); return UTF8Decoder.decode(HEAPU8.subarray(ptr, end));
} }
function stringToUTF8Array(str, heap, outIdx, maxBytesToWrite) { function stringToUTF8Array(str, heap, outIdx, maxBytesToWrite) {
+1 -1
View File
@@ -319,7 +319,7 @@ async function copyFileViaIndex(
const isIndexModified = indexStat?.mtimeMs !== defaultTimeMs; const isIndexModified = indexStat?.mtimeMs !== defaultTimeMs;
if (isDestinationHardlinkedFromIndex) { if (isDestinationHardlinkedFromIndex) {
if (isIndexModified && linkStrategy.autoRepair) { if (isIndexModified && linkStrategy.autoRepair) {
atomicBehavior = 0 /* Lock */; atomicBehavior = 0; /* Lock */
indexStat = null; indexStat = null;
} }
} }
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 4.2)
project(am_i_up_ui) project(am_i_up_ui)
find_package(ament_cmake REQUIRED) find_package(ament_cmake REQUIRED)
@@ -6,7 +6,7 @@ find_package(ament_cmake REQUIRED)
add_custom_target(build ALL) add_custom_target(build ALL)
add_custom_command(TARGET build add_custom_command(TARGET build
POST_BUILD POST_BUILD
COMMAND yarn build --outDir ${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}/dist COMMAND yarn build --emptyOutDir --outDir ${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}/dist
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
) )
+373
View File
@@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
+1 -73
View File
@@ -1,73 +1 @@
# React + TypeScript + Vite ## am_i_up_ui
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## React Compiler
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
```js
export default defineConfig([
globalIgnores(["dist"]),
{
files: ["**/*.{ts,tsx}"],
extends: [
// Other configs...
// Remove tseslint.configs.recommended and replace with this
tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
tseslint.configs.stylisticTypeChecked,
// Other configs...
],
languageOptions: {
parserOptions: {
project: ["./tsconfig.node.json", "./tsconfig.app.json"],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
]);
```
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
```js
// eslint.config.js
import reactX from "eslint-plugin-react-x";
import reactDom from "eslint-plugin-react-dom";
export default defineConfig([
globalIgnores(["dist"]),
{
files: ["**/*.{ts,tsx}"],
extends: [
// Other configs...
// Enable lint rules for React
reactX.configs["recommended-typescript"],
// Enable lint rules for React DOM
reactDom.configs.recommended,
],
languageOptions: {
parserOptions: {
project: ["./tsconfig.node.json", "./tsconfig.app.json"],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
]);
```
+4
View File
@@ -16,11 +16,14 @@
"bootstrap": "^5.3.8", "bootstrap": "^5.3.8",
"jotai": "^2.18.0", "jotai": "^2.18.0",
"jotai-tanstack-query": "^0.11.0", "jotai-tanstack-query": "^0.11.0",
"mediamtx-webrtc-react": "^1.0.1",
"prettier": "^3.8.1", "prettier": "^3.8.1",
"react": "^19.2.0", "react": "^19.2.0",
"react-bootstrap": "^2.10.10", "react-bootstrap": "^2.10.10",
"react-bootstrap-icons": "^1.11.6",
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
"react-router-dom": "^7.13.1", "react-router-dom": "^7.13.1",
"socket.io-client": "^4.8.3",
"yaml": "^2.8.2" "yaml": "^2.8.2"
}, },
"devDependencies": { "devDependencies": {
@@ -30,6 +33,7 @@
"@types/react": "^19.2.7", "@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1", "@vitejs/plugin-react": "^5.1.1",
"babel-plugin-react-compiler": "^1.0.0",
"eslint": "^9.39.1", "eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24", "eslint-plugin-react-refresh": "^0.4.24",
+13 -8
View File
@@ -1,10 +1,19 @@
//
// Copyright 2026 James Pace
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// This Source Code Form is "Incompatible With Secondary Licenses", as
// defined by the Mozilla Public License, v. 2.0.
//
import { useState } from "react"; import { useState } from "react";
import { createBrowserRouter, RouterProvider } from "react-router-dom"; import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { Provider as JotaiProvider } from "jotai"; import { Provider as JotaiProvider } from "jotai";
import { Home } from "./Home.tsx"; import { Home } from "./Home.tsx";
import { Autonomy } from "./Autonomy.tsx"; import { Video } from "./Video.tsx";
import { Version } from "./Version.tsx"; import { Version } from "./Version.tsx";
import { WhoAmI } from "./WhoAmI.tsx";
import "./App.css"; import "./App.css";
function App() { function App() {
@@ -26,17 +35,13 @@ const router = createBrowserRouter([
path: "/index.html", path: "/index.html",
element: <Home />, element: <Home />,
}, },
{
path: "/whoami",
element: <WhoAmI />,
},
{ {
path: "/version", path: "/version",
element: <Version />, element: <Version />,
}, },
{ {
path: "/autonomy", path: "/video",
element: <Autonomy />, element: <Video />,
}, },
]); ]);
-97
View File
@@ -1,97 +0,0 @@
import { AppNav, Footer } from "./AppNav.tsx";
import { Container, Row, Col, Image, Card } from "react-bootstrap";
import { atom, useAtomValue } from "jotai";
import { atomWithQuery } from "jotai-tanstack-query";
const costmapImageQuery = async () => {
const resp = await fetch("api/costmap_image");
if (!resp.ok) {
throw new Error("Network response was not ok");
}
const blob = await resp.blob();
return URL.createObjectURL(blob);
};
const costmapImageQueryAtom = atomWithQuery(() => ({
queryKey: ["costmap_image"],
queryFn: costmapImageQuery,
refetchInterval: 1000, // 1s
}));
const costmapImageAtom = atom((get) => {
const response = get(costmapImageQueryAtom);
if (response.isPending) {
return <p>"Loading..."</p>;
}
if (response.isError) {
return <p>"Error loading!"</p>;
}
const costmapStyle = {
minHeight: "65svb",
};
return <Image src={response.data} fluid style={costmapStyle} />;
});
const statusStringQuery = async () => {
const resp = await fetch("api/status");
if (!resp.ok) {
throw new Error("Network response was not ok");
}
return resp.json();
};
const statusStringQueryAtom = atomWithQuery(() => ({
queryKey: ["status_string"],
queryFn: statusStringQuery,
refetchInterval: 500, // 0.5s
}));
const statusStringAtom = atom((get) => {
const response = get(statusStringQueryAtom);
if (response.isPending || response.isError || !response.data.status) {
return "N/A";
}
return response.data.message;
});
const latLongQuery = async () => {
const resp = await fetch("api/position");
if (!resp.ok) {
throw new Error("Network response was not ok");
}
return resp.json();
};
const latLongQueryAtom = atomWithQuery(() => ({
queryKey: ["latlong"],
queryFn: latLongQuery,
refetchInterval: 500, // 0.5s
}));
const latLongAtom = atom((get) => {
const response = get(latLongQueryAtom);
if (response.isPending || response.isError || !response.data.status) {
return "N/A";
}
return `(${response.data.latitude}, ${response.data.longitude})`;
});
export function Autonomy() {
const costmapImage = useAtomValue(costmapImageAtom);
const statusString = useAtomValue(statusStringAtom);
const latLong = useAtomValue(latLongAtom);
return (
<div>
<AppNav />
<Container className="vert-padded">
<Card className="padded">
<Row>
<Col lg={8}>{costmapImage}</Col>
<Col lg={4}>
<p>Status:</p>
<pre>{statusString}</pre>
<p>Latitude, Longitude:</p>
<pre>{latLong}</pre>
</Col>
</Row>
</Card>
</Container>
<Footer />
</div>
);
}
+73 -8
View File
@@ -1,18 +1,83 @@
import { AppNav, Footer } from "./AppNav.tsx"; //
import { Container, Row, Col, Image, Card } from "react-bootstrap"; // Copyright 2026 James Pace
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// This Source Code Form is "Incompatible With Secondary Licenses", as
// defined by the Mozilla Public License, v. 2.0.
//
import { AppNav, Footer } from "./components/AppNav.tsx";
import { Container, Row, Col, Card } from "react-bootstrap";
import { atom, useAtomValue } from "jotai"; import { atom, useAtomValue } from "jotai";
import { atomWithQuery } from "jotai-tanstack-query"; import { createQueryAtom } from "./utils/query.ts";
import { DiagnosticsCard } from "./components/DiagnosticsCard.tsx";
const identityStringQueryAtom = createQueryAtom("api/identity", {
refetchInterval: 500,
});
const identityStringAtom = atom((get) => {
const response = get(identityStringQueryAtom);
if (response.isPending || response.isError) {
return "N/A";
}
return response.data.message;
});
const uptimeQueryAtom = createQueryAtom("api/uptime", {
refetchInterval: 1000,
});
const uptimeAtom = atom((get) => {
const response = get(uptimeQueryAtom);
if (response.isPending || response.isError) {
return "N/A";
}
return response.data.uptime.toFixed(1);
});
function IdentityCard() {
const identityString = useAtomValue(identityStringAtom);
return (
<Card className="padded h-100">
<Card.Title>Identity</Card.Title>
<Card.Body>
<div style={{ whiteSpace: "pre-line" }}>{identityString}</div>
</Card.Body>
</Card>
);
}
function UptimeCard() {
const uptime = useAtomValue(uptimeAtom);
return (
<Card className="padded h-100">
<Card.Title>Uptime</Card.Title>
<Card.Body>
<p>{uptime}s</p>
</Card.Body>
</Card>
);
}
export function Home() { export function Home() {
return ( return (
<div> <div>
<AppNav /> <AppNav />
<Container className="vert-padded"> <Container className="vert-padded">
<Card className="padded"> <Row>
<Row> <Col>
<p>Hello World!</p> <UptimeCard />
</Row> </Col>
</Card> <Col>
<IdentityCard />
</Col>
</Row>
<Row className="vert-padded">
<Col>
<DiagnosticsCard />
</Col>
</Row>
</Container> </Container>
<Footer /> <Footer />
</div> </div>
+52 -29
View File
@@ -1,49 +1,72 @@
import { AppNav, Footer } from "./AppNav.tsx"; //
// Copyright 2026 James Pace
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// This Source Code Form is "Incompatible With Secondary Licenses", as
// defined by the Mozilla Public License, v. 2.0.
//
import { AppNav, Footer } from "./components/AppNav.tsx";
import { Container, Row, Col, Card } from "react-bootstrap"; import { Container, Row, Col, Card } from "react-bootstrap";
import { atom, useAtomValue } from "jotai"; import { atom, useAtomValue } from "jotai";
import { atomWithQuery } from "jotai-tanstack-query"; import { createQueryAtom } from "./utils/query.ts";
import YAML from "yaml"; import YAML from "yaml";
const versionQueryFn = async () => { const buildInfoQueryAtom = createQueryAtom("api/build_info");
const resp = await fetch("api/build_info"); const buildInfoAtom = atom((get) => {
if (!resp.ok) { const buildInfo = get(buildInfoQueryAtom);
throw new Error("Network response was not ok"); if (buildInfo.isPending) {
}
return resp.json();
};
const versionQueryAtom = atomWithQuery(() => ({
queryKey: ["version"],
queryFn: versionQueryFn,
}));
const versionAtom = atom((get) => {
const version = get(versionQueryAtom);
if (version.isPending) {
return "Loading..."; return "Loading...";
} }
if (version.isError) { if (buildInfo.isError) {
return "Error loading!"; return "Error loading!";
} }
if (!version.data.status) { if (!buildInfo.data.status) {
return "Can not find version"; return "Can not find buildInfo";
} }
return YAML.stringify(version.data.message); return YAML.stringify(buildInfo.data.message);
});
const versionNumberQueryAtom = createQueryAtom("api/version_number");
const versionNumberAtom = atom((get) => {
const versionNumber = get(versionNumberQueryAtom);
if (versionNumber.isPending || versionNumber.isError) {
return "...";
}
if (!versionNumber.data.status) {
return "Can not find version number.";
}
return versionNumber.data.version;
}); });
export function Version() { export function Version() {
let versionText = useAtomValue(versionAtom); let buildInfoText = useAtomValue(buildInfoAtom);
let versionText = useAtomValue(versionNumberAtom);
return ( return (
<div> <div>
<AppNav /> <AppNav />
<Container className="vert-padded"> <Container className="vert-padded">
<Card className="padded"> <Row>
<Card.Title>project.yaml</Card.Title> <Col>
<Card.Body> <Card className="padded">
<pre>{versionText}</pre> <Card.Title>Version Number</Card.Title>
</Card.Body> <Card.Body>{versionText}</Card.Body>
</Card> </Card>
</Col>
</Row>
<Row className="vert-padded">
<Col>
<Card className="padded">
<Card.Title>project.yaml</Card.Title>
<Card.Body>
<pre>{buildInfoText}</pre>
</Card.Body>
</Card>
</Col>
</Row>
</Container> </Container>
<Footer /> <Footer />
</div> </div>
+105
View File
@@ -0,0 +1,105 @@
//
// Copyright 2026 James Pace
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// This Source Code Form is "Incompatible With Secondary Licenses", as
// defined by the Mozilla Public License, v. 2.0.
//
import { AppNav, Footer } from "./components/AppNav.tsx";
import { Container, Row, Col, Card } from "react-bootstrap";
import { MediaMtxPlayer } from "./external/MediaMtxPlayer.tsx";
import { atom, useAtomValue, useAtom } from "jotai";
import { createQueryAtom } from "./utils/query.ts";
const tokenQueryAtom = createQueryAtom("api/mediamtx/login", { retry: true });
const tokenAtom = atom((get) => {
const response = get(tokenQueryAtom);
if (response.isPending || response.isError) {
return null;
}
return response.data.token;
});
const streamsQueryAtom = createQueryAtom("api/available_streams");
const streamsAtom = atom((get) => {
const response = get(streamsQueryAtom);
if (response.isPending || response.isError) {
return [];
}
return response.data.streams;
});
const videoDisplayPortAtom = atom((get) => {
const response = get(streamsQueryAtom);
if (response.isPending || response.isError) {
return null;
}
return response.data.port;
});
const selectedStreamAtom = atom("image");
const imageStreamURIAtom = atom((get) => {
const port = get(videoDisplayPortAtom);
const selectedStream = get(selectedStreamAtom);
if (port === null || selectedStream === null) {
return null;
}
const imageStreamURI =
"http://" +
window.location.hostname +
":" +
port +
"/" +
selectedStream +
"/whep";
return imageStreamURI;
});
export function Video() {
const streamName = "image";
const imageStreamURI = useAtomValue(imageStreamURIAtom);
const token = useAtomValue(tokenAtom);
const availableStreams = useAtomValue(streamsAtom);
const [selectedStream, setSelectedStream] = useAtom(selectedStreamAtom);
const handleSelect = (event) => {
setSelectedStream(event.target.value);
};
return (
<div>
<AppNav />
<Container className="vert-padded">
<Card className="padded">
<Row>
<Col lg={8}>
<MediaMtxPlayer url={imageStreamURI} token={token} />
</Col>
<Col lg={4}>
<p>Available Streams: </p>
{availableStreams.map((stream, index) => (
<div style={{ display: "flex", gap: "8px" }}>
<input
type="radio"
name="selected_stream"
value={stream}
checked={selectedStream === stream}
onChange={handleSelect}
/>
{stream}
</div>
))}
</Col>
</Row>
</Card>
</Container>
<Footer />
</div>
);
}
-14
View File
@@ -1,14 +0,0 @@
import { AppNav, Footer } from "./AppNav.tsx";
import { Container, Row, Col, Image } from "react-bootstrap";
export function WhoAmI() {
return (
<div>
<AppNav />
<Container>
<p> Hello World! </p>
</Container>
<Footer />
</div>
);
}
+14 -7
View File
@@ -1,3 +1,13 @@
//
// Copyright 2026 James Pace
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// This Source Code Form is "Incompatible With Secondary Licenses", as
// defined by the Mozilla Public License, v. 2.0.
//
import { Navbar, Nav, Container } from "react-bootstrap"; import { Navbar, Nav, Container } from "react-bootstrap";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
@@ -6,7 +16,7 @@ export function AppNav() {
<Navbar bg="dark" variant="dark" expand="sm" className="py-0"> <Navbar bg="dark" variant="dark" expand="sm" className="py-0">
<Container className="me-auto"> <Container className="me-auto">
<Navbar.Brand as={Link} to="/"> <Navbar.Brand as={Link} to="/">
J7s-Bridge am_i_up
</Navbar.Brand> </Navbar.Brand>
<Navbar.Toggle aria-controls="collapse-navbar-nav" /> <Navbar.Toggle aria-controls="collapse-navbar-nav" />
<Navbar.Collapse id="collapse-navbar-nav"> <Navbar.Collapse id="collapse-navbar-nav">
@@ -14,15 +24,12 @@ export function AppNav() {
<Nav.Link as={Link} to="/"> <Nav.Link as={Link} to="/">
Home Home
</Nav.Link> </Nav.Link>
<Nav.Link as={Link} to="/video">
Video
</Nav.Link>
<Nav.Link as={Link} to="/version"> <Nav.Link as={Link} to="/version">
Version Version
</Nav.Link> </Nav.Link>
<Nav.Link as={Link} to="/whoami">
WhoAmI
</Nav.Link>
<Nav.Link as={Link} to="/autonomy">
Autonomy
</Nav.Link>
</Nav> </Nav>
</Navbar.Collapse> </Navbar.Collapse>
</Container> </Container>
+197
View File
@@ -0,0 +1,197 @@
//
// Copyright 2026 James Pace
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// This Source Code Form is "Incompatible With Secondary Licenses", as
// defined by the Mozilla Public License, v. 2.0.
//
import React, { useState } from "react";
import { Button, ListGroup, Collapse } from "react-bootstrap";
import {
Dash,
ChevronDown,
ChevronRight,
CircleFill,
} from "react-bootstrap-icons";
import { Container, Row, Col, Table, Card } from "react-bootstrap";
import { atom, useAtomValue } from "jotai";
import { createQueryAtom } from "../utils/query.ts";
const diagQueryAtom = createQueryAtom("api/diagnostics", {
refetchInterval: 1000,
});
const diagAtom = atom((get) => {
const diag = get(diagQueryAtom);
if (diag.isPending) {
return "Loading...";
}
if (diag.isError) {
return "Error loading!";
}
if (!diag.data.status) {
return "Failed to get diagnostic information.";
}
return diag.data.message;
});
export function DiagnosticsCard() {
const diagnostics = useAtomValue(diagAtom);
const diagContent = () => {
if (typeof diagnostics === "string" || diagnostics instanceof String) {
return <pre> {diagnostics} </pre>;
}
return <TreeView data={diagnostics} />;
};
return (
<Card className="padded">
<Card.Title>Diagnostics</Card.Title>
<Card.Body>{diagContent()}</Card.Body>
</Card>
);
}
function TreeView(props: Any) {
const data = props.data;
return (
<ListGroup>
{data.children.map((node) => (
<SingleNode key={node.id} node={node} />
))}
</ListGroup>
);
}
function LevelIndicator(props: Any) {
const level = props.level;
if (level == "ERROR") {
return <CircleFill color="red" />;
}
if (level == "WARN") {
return <CircleFill color="orange" />;
}
if (level == "OK") {
return <CircleFill color="green" />;
}
return <CircleFill color="grey" />;
}
function TitleText(props: Any) {
if (props.message === "") {
return <p>{props.name}</p>;
}
return (
<p>
{props.name}: {props.message}
</p>
);
}
function ValuesTable(props: Any) {
const values = props.values;
if (Object.keys(values).length === 0) {
return <div></div>;
}
return (
<Table>
<thead>
<th>Key</th>
<th>Value</th>
</thead>
<tbody>
{Object.entries(values).map(([key, value]) => (
<tr key={key}>
<td>{key}</td>
<td>{value}</td>
</tr>
))}
</tbody>
</Table>
);
}
function StatusContent(props: Any) {
const status = props.status;
return (
<div>
<TitleText name={status.name} message={status.message} />
<p>
<ValuesTable values={status.values} />
</p>
</div>
);
}
function DirectionIcon(props: Any) {
let icon = <ChevronRight />;
if (props.isOpen) {
icon = <ChevronDown />;
}
if (!props.hasChildren) {
icon = <Dash />;
}
return (
<Button variant="link" onClick={props.onClick}>
{icon}
</Button>
);
}
function ChildStatuses(props: Any) {
const isOpen = props.isOpen;
const node = props.node;
return (
<Collapse in={isOpen}>
<div>
<ListGroup>
{node.children.map((child) => (
<SingleNode key={child.id} node={child} />
))}
</ListGroup>
</div>
</Collapse>
);
}
function SingleNode(props: Any) {
const node = props.node;
const [isOpen, setIsOpen] = useState(false);
const hasChildren = node.children && node.children.length > 0;
const openFunction = () => {
if (hasChildren) {
setIsOpen(!isOpen);
}
};
return (
<ListGroup.Item>
<Container>
<Row>
<Col xs={1}>
<DirectionIcon
isOpen={isOpen}
hasChildren={hasChildren}
onClick={openFunction}
/>
</Col>
<Col xs={1}>
<LevelIndicator level={node.status.level} />
</Col>
<Col xs={"auto"}>
<StatusContent status={node.status} />
</Col>
</Row>
<Row>
<ChildStatuses isOpen={isOpen} node={node} />
</Row>
</Container>
</ListGroup.Item>
);
}
+78
View File
@@ -0,0 +1,78 @@
// From Gemini.
import React, { useEffect, useRef, useState } from "react";
import "./reader.js";
interface MediaMTXPlayerProps {
url: string;
user?: string;
pass?: string;
token?: string;
}
export const MediaMtxPlayer: React.FC<MediaMTXPlayerProps> = ({
url,
user,
pass,
token,
}) => {
const videoRef = useRef<HTMLVideoElement>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let reader: any = null;
if (videoRef.current && url) {
reader = new MediaMTXWebRTCReader({
url,
user,
pass,
token,
onError: (err: any) => {
console.error("MediaMTX WebRTC Error:", err);
setError(err);
},
onTrack: (evt: RTCTrackEvent) => {
if (videoRef.current && evt.streams[0]) {
videoRef.current.srcObject = evt.streams[0];
}
},
onDataChannel: (evt: RTCDataChannelEvent) => {
console.log("Data channel opened:", evt.channel);
},
});
}
// Cleanup when component unmounts or URL changes
return () => {
if (reader && typeof reader.close === "function") {
reader.close();
}
if (videoRef.current) {
videoRef.current.srcObject = null;
}
};
}, [url, user, pass, token]);
return (
<div style={{ position: "relative", width: "100%", maxWidth: "720px" }}>
{error && (
<div style={{ color: "red", marginBottom: "8px" }}>
Error getting video.
</div>
)}
<video
ref={videoRef}
autoPlay
playsInline
muted
controls
style={{
width: "100%",
maxWidth: "720px",
height: "auto",
backgroundColor: "#000",
}}
/>
</div>
);
};
+676
View File
@@ -0,0 +1,676 @@
"use strict";
/**
* @callback OnError
* @param {string} err - error.
*/
/**
* @callback OnTrack
* @param {RTCTrackEvent} evt - track event.
*/
/**
* @callback OnDataChannel
* @param {RTCDataChannelEvent} evt - data channel event.
*/
/**
* @typedef Conf
* @type {object}
* @property {string} url - absolute URL of the WHEP endpoint.
* @property {string} user - username.
* @property {string} pass - password.
* @property {string} token - token.
* @property {OnError} onError - called when there's an error.
* @property {OnTrack} onTrack - called when there's a track available.
* @property {OnDataChannel} onDataChannel - called when there's a data channel available.
*/
/** WebRTC/WHEP reader. */
class MediaMTXWebRTCReader {
static #RETRY_PAUSE = 2000;
#conf;
#state = "getting_codecs";
#restartTimeout = null;
#pc = null;
#offerData = null;
#sessionUrl = null;
#queuedCandidates = [];
#nonAdvertisedCodecs;
/**
* Create a MediaMTXWebRTCReader.
* @param {Conf} conf - configuration.
*/
constructor(conf) {
this.#conf = conf;
this.#getNonAdvertisedCodecs();
}
/**
* Close the reader and all its resources.
*/
close() {
this.#state = "closed";
if (this.#pc !== null) {
this.#pc.close();
}
if (this.#restartTimeout !== null) {
clearTimeout(this.#restartTimeout);
}
}
static #supportsNonAdvertisedCodec(codec, fmtp) {
return new Promise((resolve) => {
const pc = new RTCPeerConnection({ iceServers: [] });
const mediaType = "audio";
let payloadType = "";
pc.addTransceiver(mediaType, { direction: "recvonly" });
pc.createOffer()
.then((offer) => {
if (offer.sdp === undefined) {
throw new Error("SDP not present");
}
if (offer.sdp.includes(` ${codec}`)) {
// codec is advertised, there's no need to add it manually
throw new Error("already present");
}
const sections = offer.sdp.split(`m=${mediaType}`);
const payloadTypes = sections
.slice(1)
.map((s) => s.split("\r\n")[0].split(" ").slice(3))
.reduce((prev, cur) => [...prev, ...cur], []);
payloadType = this.#reservePayloadType(payloadTypes);
const lines = sections[1].split("\r\n");
lines[0] += ` ${payloadType}`;
lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} ${codec}`);
if (fmtp !== undefined) {
lines.splice(lines.length - 1, 0, `a=fmtp:${payloadType} ${fmtp}`);
}
sections[1] = lines.join("\r\n");
offer.sdp = sections.join(`m=${mediaType}`);
return pc.setLocalDescription(offer);
})
.then(() =>
pc.setRemoteDescription(
new RTCSessionDescription({
type: "answer",
sdp:
"v=0\r\n" +
"o=- 6539324223450680508 0 IN IP4 0.0.0.0\r\n" +
"s=-\r\n" +
"t=0 0\r\n" +
"a=fingerprint:sha-256 0D:9F:78:15:42:B5:4B:E6:E2:94:3E:5B:37:78:E1:4B:54:59:A3:36:3A:E5:05:EB:27:EE:8F:D2:2D:41:29:25\r\n" +
`m=${mediaType} 9 UDP/TLS/RTP/SAVPF ${payloadType}\r\n` +
"c=IN IP4 0.0.0.0\r\n" +
"a=ice-pwd:7c3bf4770007e7432ee4ea4d697db675\r\n" +
"a=ice-ufrag:29e036dc\r\n" +
"a=sendonly\r\n" +
"a=rtcp-mux\r\n" +
`a=rtpmap:${payloadType} ${codec}\r\n` +
(fmtp !== undefined ? `a=fmtp:${payloadType} ${fmtp}\r\n` : ""),
}),
),
)
.then(() => {
resolve(true);
})
.catch(() => {
resolve(false);
})
.finally(() => {
pc.close();
});
});
}
static #unquoteCredential(v) {
return JSON.parse(`"${v}"`);
}
static #linkToIceServers(links) {
return links !== null
? links.split(", ").map((link) => {
const m = link.match(
/^<(.+?)>; rel="ice-server"(; username="(.*?)"; credential="(.*?)"; credential-type="password")?/i,
);
const ret = {
urls: [m[1]],
};
if (m[3] !== undefined) {
ret.username = this.#unquoteCredential(m[3]);
ret.credential = this.#unquoteCredential(m[4]);
ret.credentialType = "password";
}
return ret;
})
: [];
}
static #parseOffer(sdp) {
const ret = {
iceUfrag: "",
icePwd: "",
medias: [],
};
for (const line of sdp.split("\r\n")) {
if (line.startsWith("m=")) {
ret.medias.push(line.slice("m=".length));
} else if (ret.iceUfrag === "" && line.startsWith("a=ice-ufrag:")) {
ret.iceUfrag = line.slice("a=ice-ufrag:".length);
} else if (ret.icePwd === "" && line.startsWith("a=ice-pwd:")) {
ret.icePwd = line.slice("a=ice-pwd:".length);
}
}
return ret;
}
static #reservePayloadType(payloadTypes) {
// everything is valid between 30 and 127, except for interval between 64 and 95
// https://chromium.googlesource.com/external/webrtc/+/refs/heads/master/call/payload_type.h#29
for (let i = 30; i <= 127; i++) {
if ((i <= 63 || i >= 96) && !payloadTypes.includes(i.toString())) {
const pl = i.toString();
payloadTypes.push(pl);
return pl;
}
}
throw Error("unable to find a free payload type");
}
static #enableStereoPcmau(payloadTypes, section) {
const lines = section.split("\r\n");
let payloadType = this.#reservePayloadType(payloadTypes);
lines[0] += ` ${payloadType}`;
lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} PCMU/8000/2`);
lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
payloadType = this.#reservePayloadType(payloadTypes);
lines[0] += ` ${payloadType}`;
lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} PCMA/8000/2`);
lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
return lines.join("\r\n");
}
static #enableMultichannelOpus(payloadTypes, section) {
const lines = section.split("\r\n");
let payloadType = this.#reservePayloadType(payloadTypes);
lines[0] += ` ${payloadType}`;
lines.splice(
lines.length - 1,
0,
`a=rtpmap:${payloadType} multiopus/48000/3`,
);
lines.splice(
lines.length - 1,
0,
`a=fmtp:${payloadType} channel_mapping=0,2,1;num_streams=2;coupled_streams=1`,
);
lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
payloadType = this.#reservePayloadType(payloadTypes);
lines[0] += ` ${payloadType}`;
lines.splice(
lines.length - 1,
0,
`a=rtpmap:${payloadType} multiopus/48000/4`,
);
lines.splice(
lines.length - 1,
0,
`a=fmtp:${payloadType} channel_mapping=0,1,2,3;num_streams=2;coupled_streams=2`,
);
lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
payloadType = this.#reservePayloadType(payloadTypes);
lines[0] += ` ${payloadType}`;
lines.splice(
lines.length - 1,
0,
`a=rtpmap:${payloadType} multiopus/48000/5`,
);
lines.splice(
lines.length - 1,
0,
`a=fmtp:${payloadType} channel_mapping=0,4,1,2,3;num_streams=3;coupled_streams=2`,
);
lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
payloadType = this.#reservePayloadType(payloadTypes);
lines[0] += ` ${payloadType}`;
lines.splice(
lines.length - 1,
0,
`a=rtpmap:${payloadType} multiopus/48000/6`,
);
lines.splice(
lines.length - 1,
0,
`a=fmtp:${payloadType} channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2`,
);
lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
payloadType = this.#reservePayloadType(payloadTypes);
lines[0] += ` ${payloadType}`;
lines.splice(
lines.length - 1,
0,
`a=rtpmap:${payloadType} multiopus/48000/7`,
);
lines.splice(
lines.length - 1,
0,
`a=fmtp:${payloadType} channel_mapping=0,4,1,2,3,5,6;num_streams=4;coupled_streams=4`,
);
lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
payloadType = this.#reservePayloadType(payloadTypes);
lines[0] += ` ${payloadType}`;
lines.splice(
lines.length - 1,
0,
`a=rtpmap:${payloadType} multiopus/48000/8`,
);
lines.splice(
lines.length - 1,
0,
`a=fmtp:${payloadType} channel_mapping=0,6,1,4,5,2,3,7;num_streams=5;coupled_streams=4`,
);
lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
return lines.join("\r\n");
}
static #enableL16(payloadTypes, section) {
const lines = section.split("\r\n");
let payloadType = this.#reservePayloadType(payloadTypes);
lines[0] += ` ${payloadType}`;
lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} L16/8000/2`);
lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
payloadType = this.#reservePayloadType(payloadTypes);
lines[0] += ` ${payloadType}`;
lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} L16/16000/2`);
lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
payloadType = this.#reservePayloadType(payloadTypes);
lines[0] += ` ${payloadType}`;
lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} L16/48000/2`);
lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
return lines.join("\r\n");
}
static #enableStereoOpus(section) {
let opusPayloadFormat = "";
const lines = section.split("\r\n");
for (let i = 0; i < lines.length; i++) {
if (
lines[i].startsWith("a=rtpmap:") &&
lines[i].toLowerCase().includes("opus/")
) {
opusPayloadFormat = lines[i].slice("a=rtpmap:".length).split(" ")[0];
break;
}
}
if (opusPayloadFormat === "") {
return section;
}
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith(`a=fmtp:${opusPayloadFormat} `)) {
if (!lines[i].includes("stereo")) {
lines[i] += ";stereo=1";
}
if (!lines[i].includes("sprop-stereo")) {
lines[i] += ";sprop-stereo=1";
}
}
}
return lines.join("\r\n");
}
static #editOffer(sdp, nonAdvertisedCodecs) {
const sections = sdp.split("m=");
const payloadTypes = sections
.slice(1)
.map((s) => s.split("\r\n")[0].split(" ").slice(3))
.reduce((prev, cur) => [...prev, ...cur], []);
for (let i = 1; i < sections.length; i++) {
if (sections[i].startsWith("audio")) {
sections[i] = this.#enableStereoOpus(sections[i]);
if (nonAdvertisedCodecs.includes("pcma/8000/2")) {
sections[i] = this.#enableStereoPcmau(payloadTypes, sections[i]);
}
if (nonAdvertisedCodecs.includes("multiopus/48000/6")) {
sections[i] = this.#enableMultichannelOpus(payloadTypes, sections[i]);
}
if (nonAdvertisedCodecs.includes("L16/48000/2")) {
sections[i] = this.#enableL16(payloadTypes, sections[i]);
}
break;
}
}
return sections.join("m=");
}
static #generateSdpFragment(od, candidates) {
const candidatesByMedia = {};
for (const candidate of candidates) {
const mid = candidate.sdpMLineIndex;
if (candidatesByMedia[mid] === undefined) {
candidatesByMedia[mid] = [];
}
candidatesByMedia[mid].push(candidate);
}
let frag = `a=ice-ufrag:${od.iceUfrag}\r\n` + `a=ice-pwd:${od.icePwd}\r\n`;
let mid = 0;
for (const media of od.medias) {
if (candidatesByMedia[mid] !== undefined) {
frag += `m=${media}\r\n` + `a=mid:${mid}\r\n`;
for (const candidate of candidatesByMedia[mid]) {
frag += `a=${candidate.candidate}\r\n`;
}
}
mid++;
}
return frag;
}
/** @param {string} err */
#handleError(err) {
if (this.#state === "running") {
if (this.#pc !== null) {
this.#pc.close();
this.#pc = null;
}
this.#offerData = null;
if (this.#sessionUrl !== null) {
fetch(this.#sessionUrl, {
method: "DELETE",
});
this.#sessionUrl = null;
}
this.#queuedCandidates = [];
this.#state = "restarting";
this.#restartTimeout = window.setTimeout(
() => this.#restart(),
MediaMTXWebRTCReader.#RETRY_PAUSE,
);
if (this.#conf.onError !== undefined) {
this.#conf.onError(`${err}, retrying in some seconds`);
}
} else if (this.#state === "getting_codecs") {
this.#state = "failed";
if (this.#conf.onError !== undefined) {
this.#conf.onError(err);
}
}
}
#restart() {
this.#restartTimeout = null;
this.#state = "running";
this.#start();
}
#getNonAdvertisedCodecs() {
Promise.all(
[
["pcma/8000/2"],
[
"multiopus/48000/6",
"channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2",
],
["L16/48000/2"],
].map((c) =>
MediaMTXWebRTCReader.#supportsNonAdvertisedCodec(c[0], c[1]).then(
(r) => (r ? c[0] : false),
),
),
)
.then((c) => c.filter((e) => e !== false))
.then((codecs) => {
if (this.#state !== "getting_codecs") {
throw new Error("closed");
}
this.#nonAdvertisedCodecs = codecs;
this.#state = "running";
this.#start();
})
.catch((err) => {
this.#handleError(err.toString());
});
}
#start() {
this.#requestICEServers()
.then((iceServers) => this.#setupPeerConnection(iceServers))
.then((offer) => this.#sendOffer(offer))
.then((answer) => this.#setAnswer(answer))
.catch((err) => {
this.#handleError(err.toString());
});
}
#authHeader() {
if (this.#conf.user !== undefined && this.#conf.user !== "") {
const credentials = btoa(`${this.#conf.user}:${this.#conf.pass}`);
return { Authorization: `Basic ${credentials}` };
}
if (this.#conf.token !== undefined && this.#conf.token !== "") {
return { Authorization: `Bearer ${this.#conf.token}` };
}
return {};
}
#requestICEServers() {
return fetch(this.#conf.url, {
method: "OPTIONS",
headers: this.#authHeader(),
}).then((res) =>
MediaMTXWebRTCReader.#linkToIceServers(res.headers.get("Link")),
);
}
#setupPeerConnection(iceServers) {
if (this.#state !== "running") {
throw new Error("closed");
}
this.#pc = new RTCPeerConnection({
iceServers,
// https://webrtc.org/getting-started/unified-plan-transition-guide
sdpSemantics: "unified-plan",
});
const direction = "recvonly";
this.#pc.addTransceiver("video", { direction });
this.#pc.addTransceiver("audio", { direction });
// using data channels requires creating a data channel locally
this.#pc.createDataChannel("");
this.#pc.onicecandidate = (evt) => this.#onLocalCandidate(evt);
this.#pc.onconnectionstatechange = () => this.#onConnectionState();
this.#pc.ontrack = (evt) => this.#onTrack(evt);
this.#pc.ondatachannel = (evt) => this.#onDataChannel(evt);
return this.#pc.createOffer().then((offer) => {
offer.sdp = MediaMTXWebRTCReader.#editOffer(
offer.sdp,
this.#nonAdvertisedCodecs,
);
this.#offerData = MediaMTXWebRTCReader.#parseOffer(offer.sdp);
return this.#pc.setLocalDescription(offer).then(() => offer.sdp);
});
}
#sendOffer(offer) {
if (this.#state !== "running") {
throw new Error("closed");
}
return fetch(this.#conf.url, {
method: "POST",
headers: {
...this.#authHeader(),
"Content-Type": "application/sdp",
},
body: offer,
}).then((res) => {
switch (res.status) {
case 201:
break;
case 404:
throw new Error("stream not found");
case 400:
return res.json().then((e) => {
throw new Error(e.error);
});
default:
throw new Error(`bad status code ${res.status}`);
}
this.#sessionUrl = new URL(
res.headers.get("location"),
this.#conf.url,
).toString();
return res.text();
});
}
#setAnswer(answer) {
if (this.#state !== "running") {
throw new Error("closed");
}
return this.#pc
.setRemoteDescription(
new RTCSessionDescription({
type: "answer",
sdp: answer,
}),
)
.then(() => {
if (this.#state !== "running") {
return;
}
if (this.#queuedCandidates.length !== 0) {
this.#sendLocalCandidates(this.#queuedCandidates);
this.#queuedCandidates = [];
}
});
}
#onLocalCandidate(evt) {
if (this.#state !== "running") {
return;
}
if (evt.candidate !== null) {
if (this.#sessionUrl === null) {
this.#queuedCandidates.push(evt.candidate);
} else {
this.#sendLocalCandidates([evt.candidate]);
}
}
}
#sendLocalCandidates(candidates) {
fetch(this.#sessionUrl, {
method: "PATCH",
headers: {
"Content-Type": "application/trickle-ice-sdpfrag",
"If-Match": "*",
},
body: MediaMTXWebRTCReader.#generateSdpFragment(
this.#offerData,
candidates,
),
})
.then((res) => {
switch (res.status) {
case 204:
break;
case 404:
throw new Error("stream not found");
default:
throw new Error(`bad status code ${res.status}`);
}
})
.catch((err) => {
this.#handleError(err.toString());
});
}
#onConnectionState() {
if (this.#state !== "running") {
return;
}
// "closed" can arrive before "failed" and without
// the close() method being called at all.
// It happens when the other peer sends a termination
// message like a DTLS CloseNotify.
if (
this.#pc.connectionState === "failed" ||
this.#pc.connectionState === "closed"
) {
this.#handleError("peer connection closed");
}
}
#onTrack(evt) {
if (this.#conf.onTrack !== undefined) {
this.#conf.onTrack(evt);
}
}
#onDataChannel(evt) {
if (this.#conf.onDataChannel !== undefined) {
this.#conf.onDataChannel(evt);
}
}
}
window.MediaMTXWebRTCReader = MediaMTXWebRTCReader;
+10
View File
@@ -1,3 +1,13 @@
//
// Copyright 2026 James Pace
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// This Source Code Form is "Incompatible With Secondary Licenses", as
// defined by the Mozilla Public License, v. 2.0.
//
import { StrictMode } from "react"; import { StrictMode } from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import App from "./App.tsx"; import App from "./App.tsx";
+33
View File
@@ -0,0 +1,33 @@
//
// Copyright 2026 James Pace
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// This Source Code Form is "Incompatible With Secondary Licenses", as
// defined by the Mozilla Public License, v. 2.0.
//
import { atomWithQuery } from "jotai-tanstack-query";
export async function fetchJson(url: string): Promise<any> {
const resp = await fetch(url);
if (!resp.ok) {
throw new Error(`Network response was not ok: ${resp.statusText}`);
}
return resp.json();
}
interface CreateQueryAtomOptions {
refetchInterval?: number;
retry?: boolean;
}
export function createQueryAtom(url: string, options?: CreateQueryAtomOptions) {
return atomWithQuery(() => ({
queryKey: [url],
queryFn: () => fetchJson(url),
...options,
}));
}
+7 -1
View File
@@ -3,7 +3,13 @@ import react from "@vitejs/plugin-react";
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [
react({
babel: {
plugins: ["babel-plugin-react-compiler"],
},
}),
],
server: { server: {
cors: false, cors: false,
}, },
+113 -3
View File
@@ -206,7 +206,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.6, @babel/types@npm:^7.29.0": "@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.26.0, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.6, @babel/types@npm:^7.29.0":
version: 7.29.0 version: 7.29.0
resolution: "@babel/types@npm:7.29.0" resolution: "@babel/types@npm:7.29.0"
dependencies: dependencies:
@@ -843,6 +843,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@socket.io/component-emitter@npm:~3.1.0":
version: 3.1.2
resolution: "@socket.io/component-emitter@npm:3.1.2"
checksum: 10c0/c4242bad66f67e6f7b712733d25b43cbb9e19a595c8701c3ad99cbeb5901555f78b095e24852f862fffb43e96f1d8552e62def885ca82ae1bb05da3668fd87d7
languageName: node
linkType: hard
"@swc/helpers@npm:^0.5.0": "@swc/helpers@npm:^0.5.0":
version: 0.5.19 version: 0.5.19
resolution: "@swc/helpers@npm:0.5.19" resolution: "@swc/helpers@npm:0.5.19"
@@ -1180,6 +1187,7 @@ __metadata:
"@types/react": "npm:^19.2.7" "@types/react": "npm:^19.2.7"
"@types/react-dom": "npm:^19.2.3" "@types/react-dom": "npm:^19.2.3"
"@vitejs/plugin-react": "npm:^5.1.1" "@vitejs/plugin-react": "npm:^5.1.1"
babel-plugin-react-compiler: "npm:^1.0.0"
bootstrap: "npm:^5.3.8" bootstrap: "npm:^5.3.8"
eslint: "npm:^9.39.1" eslint: "npm:^9.39.1"
eslint-plugin-react-hooks: "npm:^7.0.1" eslint-plugin-react-hooks: "npm:^7.0.1"
@@ -1187,11 +1195,14 @@ __metadata:
globals: "npm:^16.5.0" globals: "npm:^16.5.0"
jotai: "npm:^2.18.0" jotai: "npm:^2.18.0"
jotai-tanstack-query: "npm:^0.11.0" jotai-tanstack-query: "npm:^0.11.0"
mediamtx-webrtc-react: "npm:^1.0.1"
prettier: "npm:^3.8.1" prettier: "npm:^3.8.1"
react: "npm:^19.2.0" react: "npm:^19.2.0"
react-bootstrap: "npm:^2.10.10" react-bootstrap: "npm:^2.10.10"
react-bootstrap-icons: "npm:^1.11.6"
react-dom: "npm:^19.2.0" react-dom: "npm:^19.2.0"
react-router-dom: "npm:^7.13.1" react-router-dom: "npm:^7.13.1"
socket.io-client: "npm:^4.8.3"
typescript: "npm:~5.9.3" typescript: "npm:~5.9.3"
typescript-eslint: "npm:^8.48.0" typescript-eslint: "npm:^8.48.0"
vite: "npm:^7.3.1" vite: "npm:^7.3.1"
@@ -1215,6 +1226,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"babel-plugin-react-compiler@npm:^1.0.0":
version: 1.0.0
resolution: "babel-plugin-react-compiler@npm:1.0.0"
dependencies:
"@babel/types": "npm:^7.26.0"
checksum: 10c0/9406267ada8d7dbdfe8906b40ecadb816a5f4cee2922bee23f7729293b369624ee135b5a9b0f263851c263c9787522ac5d97016c9a2b82d1668300e42b18aff8
languageName: node
linkType: hard
"balanced-match@npm:^1.0.0": "balanced-match@npm:^1.0.0":
version: 1.0.2 version: 1.0.2
resolution: "balanced-match@npm:1.0.2" resolution: "balanced-match@npm:1.0.2"
@@ -1393,7 +1413,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.4.3": "debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.4.3, debug@npm:~4.4.1":
version: 4.4.3 version: 4.4.3
resolution: "debug@npm:4.4.3" resolution: "debug@npm:4.4.3"
dependencies: dependencies:
@@ -1436,6 +1456,26 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"engine.io-client@npm:~6.6.1":
version: 6.6.6
resolution: "engine.io-client@npm:6.6.6"
dependencies:
"@socket.io/component-emitter": "npm:~3.1.0"
debug: "npm:~4.4.1"
engine.io-parser: "npm:~5.2.1"
ws: "npm:~8.21.0"
xmlhttprequest-ssl: "npm:~2.1.1"
checksum: 10c0/805995b57b2656f52322004b4d767851b517618b1fcecabf5968c55ce6864c692e594fe3a6ae9284504aa087a5a872b0ea1b76b876dcf4cf866907bd61e9e1dd
languageName: node
linkType: hard
"engine.io-parser@npm:~5.2.1":
version: 5.2.3
resolution: "engine.io-parser@npm:5.2.3"
checksum: 10c0/ed4900d8dbef470ab3839ccf3bfa79ee518ea8277c7f1f2759e8c22a48f64e687ea5e474291394d0c94f84054749fd93f3ef0acb51fa2f5f234cc9d9d8e7c536
languageName: node
linkType: hard
"env-paths@npm:^2.2.0": "env-paths@npm:^2.2.0":
version: 2.2.1 version: 2.2.1
resolution: "env-paths@npm:2.2.1" resolution: "env-paths@npm:2.2.1"
@@ -2157,6 +2197,21 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"mediamtx-webrtc-react@npm:^1.0.1":
version: 1.0.1
resolution: "mediamtx-webrtc-react@npm:1.0.1"
peerDependencies:
"@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
"@types/react":
optional: true
react:
optional: true
checksum: 10c0/ee1e630f203b94ddba9e2caa20187f05989098814cc6efd89a0c32e75e887dc3a536ca74ea7ed26689ef7dd732fa0f18b3222570ae915eb86fcf70ae665f662d
languageName: node
linkType: hard
"minimatch@npm:^10.2.2": "minimatch@npm:^10.2.2":
version: 10.2.4 version: 10.2.4
resolution: "minimatch@npm:10.2.4" resolution: "minimatch@npm:10.2.4"
@@ -2458,7 +2513,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"prop-types@npm:^15.6.2, prop-types@npm:^15.8.1": "prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1":
version: 15.8.1 version: 15.8.1
resolution: "prop-types@npm:15.8.1" resolution: "prop-types@npm:15.8.1"
dependencies: dependencies:
@@ -2476,6 +2531,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"react-bootstrap-icons@npm:^1.11.6":
version: 1.11.6
resolution: "react-bootstrap-icons@npm:1.11.6"
dependencies:
prop-types: "npm:^15.7.2"
peerDependencies:
react: ">=16.8.6"
checksum: 10c0/4dfd75c980a4431032e7d861d52efeb18cb141e00300ae1cf055dcfcb1fd7689991d8b467c38f1103563662bbbd61aaf247093e7c04f6f1ad0aae86feff5047d
languageName: node
linkType: hard
"react-bootstrap@npm:^2.10.10": "react-bootstrap@npm:^2.10.10":
version: 2.10.10 version: 2.10.10
resolution: "react-bootstrap@npm:2.10.10" resolution: "react-bootstrap@npm:2.10.10"
@@ -2752,6 +2818,28 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"socket.io-client@npm:^4.8.3":
version: 4.8.3
resolution: "socket.io-client@npm:4.8.3"
dependencies:
"@socket.io/component-emitter": "npm:~3.1.0"
debug: "npm:~4.4.1"
engine.io-client: "npm:~6.6.1"
socket.io-parser: "npm:~4.2.4"
checksum: 10c0/76c0d86de0b636d0bf5011cf3425212c900f43dac632db6eb493816920cd035af7ddd92fea9a106b45eb347405953c0414eb3a5d465b180215e46fc8b61420b3
languageName: node
linkType: hard
"socket.io-parser@npm:~4.2.4":
version: 4.2.7
resolution: "socket.io-parser@npm:4.2.7"
dependencies:
"@socket.io/component-emitter": "npm:~3.1.0"
debug: "npm:~4.4.1"
checksum: 10c0/16a5579718b871114ca644d688987dd3cf9292010c949fb083633eefbc1d8fdaf424691d6511d746e5c10f5c88cbd8911cd0b1e807242159f40989be90842b4e
languageName: node
linkType: hard
"socks-proxy-agent@npm:^8.0.3": "socks-proxy-agent@npm:^8.0.3":
version: 8.0.5 version: 8.0.5
resolution: "socks-proxy-agent@npm:8.0.5" resolution: "socks-proxy-agent@npm:8.0.5"
@@ -3052,6 +3140,28 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"ws@npm:~8.21.0":
version: 8.21.3
resolution: "ws@npm:8.21.3"
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ">=5.0.2"
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
checksum: 10c0/7b28dc2863ea0e2cece68d142a3eee90361021b73f750431e6d8076bb7dede5fdfdb75b3d29534b62f411261147b76b1bc80fb5cc63ab0aabd467280e85b22e0
languageName: node
linkType: hard
"xmlhttprequest-ssl@npm:~2.1.1":
version: 2.1.2
resolution: "xmlhttprequest-ssl@npm:2.1.2"
checksum: 10c0/70d60869323e823f473a238f78fd108437edbc3690ecd5859c39c83217080090a18899b272e515769c0d1f518cc64cbed6b6995b23fdd7ba13b297d530b6e631
languageName: node
linkType: hard
"yallist@npm:^3.0.2": "yallist@npm:^3.0.2":
version: 3.1.1 version: 3.1.1
resolution: "yallist@npm:3.1.1" resolution: "yallist@npm:3.1.1"