diff --git a/backend/lib/edgehog/devices/device/device.ex b/backend/lib/edgehog/devices/device/device.ex index 49b330c2b..d70ea647e 100644 --- a/backend/lib/edgehog/devices/device/device.ex +++ b/backend/lib/edgehog/devices/device/device.ex @@ -62,6 +62,34 @@ defmodule Edgehog.Devices.Device do paginate_relationship_with application_deployments: :relay, ota_operations: :relay, tags: :relay + + subscriptions do + pubsub EdgehogWeb.Endpoint + + subscribe :device_created do + action_types :create + end + + subscribe :device_updated do + action_types :update + end + + subscribe :device_connected do + actions [:from_device_connected_event] + end + + subscribe :device_disconnected do + actions [:from_device_disconnected_event] + end + + subscribe :device_tags_updated do + actions [:add_tags, :remove_tags] + end + + subscribe :device_deleted do + action_types :destroy + end + end end actions do diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 223c7ffd0..50b1fe4e9 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,6 +9,9 @@ "version": "0.10.0-alpha.8", "license": "Apache-2.0", "dependencies": { + "@absinthe/socket": "^0.2.1", + "@absinthe/socket-relay": "^0.2.1", + "@apollo/client": "^4.0.9", "@formatjs/cli": "^6.2.12", "@fortawesome/fontawesome-svg-core": "^6.5.2", "@fortawesome/free-brands-svg-icons": "^6.5.2", @@ -30,10 +33,12 @@ "bootstrap": "^5.3.3", "dayjs": "^1.11.11", "graphql": "^16.9.0", + "graphql-ws": "^6.0.6", "history": "^5.1.0", "js-cookie": "^3.0.5", "leaflet": "^1.9.4", "lodash": "^4.17.21", + "phoenix": "^1.8.1", "react": "^18.3.1", "react-apexcharts": "^1.8.0", "react-bootstrap": "^2.10.4", @@ -78,6 +83,155 @@ "vitest": "^1.6.0" } }, + "node_modules/@absinthe/socket": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@absinthe/socket/-/socket-0.2.1.tgz", + "integrity": "sha512-rCuMRG4WndooGR+QfU5v+xL6U8YKEXFyvjqYt0qTHupAh+k+tpD6a5dlxcLO0g38p/hb1I12OzKvl+0G1XYCkA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "7.2.0", + "@jumpn/utils-array": "0.3.4", + "@jumpn/utils-composite": "0.7.0", + "@jumpn/utils-graphql": "0.6.0", + "core-js": "2.6.0", + "zen-observable": "0.8.11" + }, + "peerDependencies": { + "phoenix": "^1.4.0" + } + }, + "node_modules/@absinthe/socket-relay": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@absinthe/socket-relay/-/socket-relay-0.2.1.tgz", + "integrity": "sha512-IbEeJEF6dqwD8pUfIM5an3hZUYMw8X8KTQP/UP1yShyMkojDygz95IvMQR1rESjtfh3yfc12jSF3ao5GanDBaw==", + "license": "MIT", + "dependencies": { + "@absinthe/socket": "0.2.1", + "@babel/runtime": "7.2.0", + "@jumpn/utils-graphql": "0.6.0", + "@jumpn/utils-promise": "0.3.1", + "core-js": "2.6.0", + "react-relay": "1.7.0" + } + }, + "node_modules/@absinthe/socket-relay/node_modules/@babel/runtime": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.2.0.tgz", + "integrity": "sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.12.0" + } + }, + "node_modules/@absinthe/socket-relay/node_modules/fbjs": { + "version": "0.8.17", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", + "integrity": "sha512-Q1MvLM+cllhk7lv9Pci7dIdpC5W8MS6W0slOWizKG66+te0m9/YqjfIt41rKmH+Nqz+mMiGgdEVonDadPyKnug==", + "license": "MIT", + "dependencies": { + "core-js": "^1.0.0", + "isomorphic-fetch": "^2.1.1", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^0.7.18" + } + }, + "node_modules/@absinthe/socket-relay/node_modules/fbjs/node_modules/core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha512-ZiPp9pZlgxpWRu0M+YWbm6+aQ84XEfH1JRXvfOc/fILWI0VKhLC2LX13X1NYq4fULzLMq7Hfh43CSo2/aIaUPA==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "license": "MIT" + }, + "node_modules/@absinthe/socket-relay/node_modules/react": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@absinthe/socket-relay/node_modules/react-relay": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/react-relay/-/react-relay-1.7.0.tgz", + "integrity": "sha512-vZOs1iK6LxqeaAelwSuD5eVXnQux5eVIrik/kxKt6Y3j6ylrjrdTadmgO6sapGc0TG61VtFK5CKPOtW+XSNotg==", + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.23.0", + "fbjs": "0.8.17", + "prop-types": "^15.5.8", + "relay-runtime": "1.7.0" + }, + "peerDependencies": { + "react": "^16.3.0" + } + }, + "node_modules/@absinthe/socket-relay/node_modules/regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==", + "license": "MIT" + }, + "node_modules/@absinthe/socket-relay/node_modules/relay-runtime": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/relay-runtime/-/relay-runtime-1.7.0.tgz", + "integrity": "sha512-gvx01aRoLHdIMQoIjMQ79js4BR9JZVfF/SoSiLXvWOgDWEnD7RKb80zmCZTByCpka0GwFzkVwBWUy1gW6g0zlQ==", + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.23.0", + "fbjs": "0.8.17" + } + }, + "node_modules/@absinthe/socket-relay/node_modules/ua-parser-js": { + "version": "0.7.41", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.41.tgz", + "integrity": "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@absinthe/socket/node_modules/@babel/runtime": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.2.0.tgz", + "integrity": "sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.12.0" + } + }, + "node_modules/@absinthe/socket/node_modules/regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==", + "license": "MIT" + }, "node_modules/@adobe/css-tools": { "version": "4.4.4", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", @@ -99,6 +253,48 @@ "node": ">=6.0.0" } }, + "node_modules/@apollo/client": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@apollo/client/-/client-4.0.9.tgz", + "integrity": "sha512-Lh2drMzFE9x5jVS8RKmlGL5SORkvpyUJIT+wTErxDUR2HpWePiBfhhcHHRSlZFiCR866ewCv4euTc4IDF0GWxw==", + "license": "MIT", + "workspaces": [ + "dist", + "codegen", + "scripts/codemods/ac3-to-ac4" + ], + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "@wry/caches": "^1.0.0", + "@wry/equality": "^0.5.6", + "@wry/trie": "^0.5.0", + "graphql-tag": "^2.12.6", + "optimism": "^0.18.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "graphql": "^16.0.0", + "graphql-ws": "^5.5.5 || ^6.0.3", + "react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc", + "react-dom": "^17.0.0 || ^18.0.0 || >=19.0.0-rc", + "rxjs": "^7.3.0", + "subscriptions-transport-ws": "^0.9.0 || ^0.11.0" + }, + "peerDependenciesMeta": { + "graphql-ws": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "subscriptions-transport-ws": { + "optional": true + } + } + }, "node_modules/@asamuzakjp/css-color": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", @@ -1328,6 +1524,15 @@ "react": "^16.3 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/@graphql-typed-document-node/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", + "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "license": "MIT", + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, "node_modules/@hookform/resolvers": { "version": "2.9.11", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-2.9.11.tgz", @@ -1462,6 +1667,105 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jumpn/utils-array": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@jumpn/utils-array/-/utils-array-0.3.4.tgz", + "integrity": "sha512-ExRwf0b0NMyMn6HLStMeqEEtmblV0BKVZ4YT3FhEJ5ErZSPN4Vv6xYUJQGNG0b7QGZJIN2KetxEoOm4MYmXygw==", + "license": "MIT", + "dependencies": { + "babel-polyfill": "6.26.0", + "babel-runtime": "6.26.0", + "flow-static-land": "0.2.7" + } + }, + "node_modules/@jumpn/utils-composite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@jumpn/utils-composite/-/utils-composite-0.7.0.tgz", + "integrity": "sha512-kamRVYJLNvjMrnKKeu2RSFQHLUO/IYFo05gLI7GQcCk063mJzsjCCfRycCievIBI+5Sg8C7A5gwRYxkBA5jY8w==", + "license": "MIT", + "dependencies": { + "@jumpn/utils-array": "0.3.4", + "babel-polyfill": "6.26.0", + "babel-runtime": "6.26.0", + "fast-deep-equal": "1.0.0", + "flow-static-land": "0.2.8" + } + }, + "node_modules/@jumpn/utils-composite/node_modules/fast-deep-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", + "integrity": "sha512-46+Jxk9Yj/nQY+3a1KTnpbBTemcAbPySTKya8iM9D7EsiONpSWbvzesalcCJ6tmJrCUITT2fmAQfNHFG+OHM6Q==", + "license": "MIT" + }, + "node_modules/@jumpn/utils-composite/node_modules/flow-static-land": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/flow-static-land/-/flow-static-land-0.2.8.tgz", + "integrity": "sha512-pOZFExu2rbscCgcEo7nL7FNhBubMi18dn1Un4lm8LOmQkYhgsHLsrBGMWmuJXRWcYMrOC7I/bPsiqqVjdD3K1g==", + "license": "MIT" + }, + "node_modules/@jumpn/utils-graphql": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jumpn/utils-graphql/-/utils-graphql-0.6.0.tgz", + "integrity": "sha512-I5OSEh8Ed4FdLIcUTYzWdpO9noQOoWptdgF8yOZ0xhDD7h7E9IgPYxfy36qbC6v9xlpGTwQMu3Wn8ulkinG/MQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "7.2.0", + "core-js": "2.6.0", + "graphql": "14.0.2" + } + }, + "node_modules/@jumpn/utils-graphql/node_modules/@babel/runtime": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.2.0.tgz", + "integrity": "sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.12.0" + } + }, + "node_modules/@jumpn/utils-graphql/node_modules/graphql": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.0.2.tgz", + "integrity": "sha512-gUC4YYsaiSJT1h40krG3J+USGlwhzNTXSb4IOZljn9ag5Tj+RkoXrWp+Kh7WyE3t1NCfab5kzCuxBIvOMERMXw==", + "deprecated": "No longer supported; please update to a newer version. Details: https://github.com/graphql/graphql-js#version-support", + "license": "MIT", + "dependencies": { + "iterall": "^1.2.2" + }, + "engines": { + "node": ">= 6.x" + } + }, + "node_modules/@jumpn/utils-graphql/node_modules/regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==", + "license": "MIT" + }, + "node_modules/@jumpn/utils-promise": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@jumpn/utils-promise/-/utils-promise-0.3.1.tgz", + "integrity": "sha512-JiaAXtcdm4PlBLToV55g0y9rFgXxoTzxW8WsKeV5T5gcVoDCA7rmISZxGjA3mv28QfdctnJO0HddAuL+gORvTQ==", + "license": "MIT", + "dependencies": { + "@jumpn/utils-composite": "0.4.2", + "babel-polyfill": "6.26.0", + "babel-runtime": "6.26.0", + "flow-static-land": "0.2.7" + } + }, + "node_modules/@jumpn/utils-promise/node_modules/@jumpn/utils-composite": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@jumpn/utils-composite/-/utils-composite-0.4.2.tgz", + "integrity": "sha512-aDo9OyzzG62J1xylYEWRSjn6bwTjy1agEVAae1I2lZqMPp1QsLHbDoCqjfil40TpiyyYuCyZ8Zl63ByC4zIF7g==", + "license": "MIT", + "dependencies": { + "@jumpn/utils-array": "0.3.4", + "babel-polyfill": "6.26.0", + "babel-runtime": "6.26.0", + "flow-static-land": "0.2.7" + } + }, "node_modules/@monaco-editor/loader": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.6.1.tgz", @@ -3040,6 +3344,54 @@ "dev": true, "license": "MIT" }, + "node_modules/@wry/caches": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@wry/caches/-/caches-1.0.1.tgz", + "integrity": "sha512-bXuaUNLVVkD20wcGBWRyo7j9N3TxePEWFZj2Y+r9OoUzfqmavM84+mFykRicNsBqatba5JLay1t48wxaXaWnlA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wry/context": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.7.4.tgz", + "integrity": "sha512-jmT7Sb4ZQWI5iyu3lobQxICu2nC/vbUhP0vIdd6tHC9PTfenmRmuIFqktc6GH9cgi+ZHnsLWPvfSvc4DrYmKiQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wry/equality": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.5.7.tgz", + "integrity": "sha512-BRFORjsTuQv5gxcXsuDXx6oGRhuVsEGwZy6LOzRRfgu+eSfxbhUQ9L9YtSEIuIjY/o7g3iWFjrc5eSY1GXP2Dw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wry/trie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.5.0.tgz", + "integrity": "sha512-FNoYzHawTMk/6KMQoEG5O4PuioX19UbwdQKF44yw0nLfOypfQdjtfZzo/UIJWAJ23sNIFbD1Ug9lbaDGMwbqQA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@yr/monotone-cubic-spline": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", @@ -3210,6 +3562,33 @@ "npm": ">=6" } }, + "node_modules/babel-polyfill": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha512-F2rZGQnAdaHWQ8YAoeRbukc7HS9QgdgeyJ0rQDd485v9opwuPvjpPFcOOT/WmkKTdgy9ESgSPXDcTNpzrGr6iQ==", + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" + } + }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "license": "MIT", + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-runtime/node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "license": "MIT" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3473,6 +3852,13 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "license": "MIT" }, + "node_modules/core-js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.0.tgz", + "integrity": "sha512-kLRC6ncVpuEW/1kwrOXYX6KQASCVtrh1gQr/UiaVgFlf9WE5Vp+lNe5+h3LuMr5PAucWnnEXwH0nQHRH/gpGtw==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "license": "MIT" + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -3720,6 +4106,15 @@ "integrity": "sha512-OBwbZjWgrCOH+g6uJsA2/7Twpas2OlepS9uvByJjR2datRDuKGYeD+nP8lBBks2qnB7bGJNHDUx7c/YLaT3QMQ==", "license": "ISC" }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, "node_modules/entities": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", @@ -4402,6 +4797,12 @@ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "license": "ISC" }, + "node_modules/flow-static-land": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/flow-static-land/-/flow-static-land-0.2.7.tgz", + "integrity": "sha512-SmGgln4qcqLAysXM5BF8EQS+clj0TUf9+KlZUJgwuzNHvwbput+opz3CMyee9sDuLf4J/3sJbYGjdNsd2jSurA==", + "license": "MIT" + }, "node_modules/form-data": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", @@ -4645,6 +5046,52 @@ "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } }, + "node_modules/graphql-tag": { + "version": "2.12.6", + "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz", + "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/graphql-ws": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-6.0.6.tgz", + "integrity": "sha512-zgfER9s+ftkGKUZgc0xbx8T7/HMO4AV5/YuYiFc+AtgcO5T0v8AxYYNQ+ltzuzDZgNkYJaFspm5MMYLjQzrkmw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@fastify/websocket": "^10 || ^11", + "crossws": "~0.3", + "graphql": "^15.10.1 || ^16", + "uWebSockets.js": "^20", + "ws": "^8" + }, + "peerDependenciesMeta": { + "@fastify/websocket": { + "optional": true + }, + "crossws": { + "optional": true + }, + "uWebSockets.js": { + "optional": true + }, + "ws": { + "optional": true + } + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -4781,7 +5228,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -4965,6 +5411,35 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha512-9c4TNAKYXM5PRyVcwUZrF3W09nQ+sO7+jydgs4ZGW9dhsLG2VOlISJABombdQqQRXCwuYG3sYV/puGf5rp0qmA==", + "license": "MIT", + "dependencies": { + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" + } + }, + "node_modules/isomorphic-fetch/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isomorphic-fetch/node_modules/node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "license": "MIT", + "dependencies": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -5019,6 +5494,12 @@ "node": ">=8" } }, + "node_modules/iterall": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", + "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==", + "license": "MIT" + }, "node_modules/js-cookie": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", @@ -5615,6 +6096,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optimism": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.18.1.tgz", + "integrity": "sha512-mLXNwWPa9dgFyDqkNi54sjDyNJ9/fTI6WGBLgnXku1vdKY/jovHfZT5r+aiVeFFLOz+foPNOm5YJ4mqgld2GBQ==", + "license": "MIT", + "dependencies": { + "@wry/caches": "^1.0.0", + "@wry/context": "^0.7.0", + "@wry/trie": "^0.5.0", + "tslib": "^2.3.0" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5764,6 +6257,13 @@ "node": "*" } }, + "node_modules/phoenix": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/phoenix/-/phoenix-1.8.1.tgz", + "integrity": "sha512-7i2a5f5y0Qhzi2g/+iuaGr4TzRt5ucqI0JeV2Q9K0swqY8L2C7CLR66RcGCpcQKGOY73B5GLKMYEl13o/9l/Cg==", + "license": "MIT", + "peer": true + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -6281,6 +6781,12 @@ "node": ">=8" } }, + "node_modules/regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w==", + "license": "MIT" + }, "node_modules/relay-compiler": { "version": "17.0.0", "resolved": "https://registry.npmjs.org/relay-compiler/-/relay-compiler-17.0.0.tgz", @@ -6447,11 +6953,20 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, "license": "MIT" }, "node_modules/sass": { @@ -7356,6 +7871,12 @@ "node": ">=18" } }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "license": "MIT" + }, "node_modules/whatwg-mimetype": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", @@ -7431,8 +7952,9 @@ "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "dev": true, + "devOptional": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -7516,9 +8038,128 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zen-observable": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.11.tgz", + "integrity": "sha512-N3xXQVr4L61rZvGMpWe8XoCGX8vhU35dPyQ4fm5CY/KDlG0F75un14hjbckPXTDuKUY6V0dqR2giT6xN8Y4GEQ==", + "license": "MIT" } }, "dependencies": { + "@absinthe/socket": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@absinthe/socket/-/socket-0.2.1.tgz", + "integrity": "sha512-rCuMRG4WndooGR+QfU5v+xL6U8YKEXFyvjqYt0qTHupAh+k+tpD6a5dlxcLO0g38p/hb1I12OzKvl+0G1XYCkA==", + "requires": { + "@babel/runtime": "7.2.0", + "@jumpn/utils-array": "0.3.4", + "@jumpn/utils-composite": "0.7.0", + "@jumpn/utils-graphql": "0.6.0", + "core-js": "2.6.0", + "zen-observable": "0.8.11" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.2.0.tgz", + "integrity": "sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg==", + "requires": { + "regenerator-runtime": "^0.12.0" + } + }, + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + } + } + }, + "@absinthe/socket-relay": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@absinthe/socket-relay/-/socket-relay-0.2.1.tgz", + "integrity": "sha512-IbEeJEF6dqwD8pUfIM5an3hZUYMw8X8KTQP/UP1yShyMkojDygz95IvMQR1rESjtfh3yfc12jSF3ao5GanDBaw==", + "requires": { + "@absinthe/socket": "0.2.1", + "@babel/runtime": "7.2.0", + "@jumpn/utils-graphql": "0.6.0", + "@jumpn/utils-promise": "0.3.1", + "core-js": "2.6.0", + "react-relay": "1.7.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.2.0.tgz", + "integrity": "sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg==", + "requires": { + "regenerator-runtime": "^0.12.0" + } + }, + "fbjs": { + "version": "0.8.17", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", + "integrity": "sha512-Q1MvLM+cllhk7lv9Pci7dIdpC5W8MS6W0slOWizKG66+te0m9/YqjfIt41rKmH+Nqz+mMiGgdEVonDadPyKnug==", + "requires": { + "core-js": "^1.0.0", + "isomorphic-fetch": "^2.1.1", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^0.7.18" + }, + "dependencies": { + "core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha512-ZiPp9pZlgxpWRu0M+YWbm6+aQ84XEfH1JRXvfOc/fILWI0VKhLC2LX13X1NYq4fULzLMq7Hfh43CSo2/aIaUPA==" + } + } + }, + "react": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "peer": true, + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + } + }, + "react-relay": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/react-relay/-/react-relay-1.7.0.tgz", + "integrity": "sha512-vZOs1iK6LxqeaAelwSuD5eVXnQux5eVIrik/kxKt6Y3j6ylrjrdTadmgO6sapGc0TG61VtFK5CKPOtW+XSNotg==", + "requires": { + "babel-runtime": "^6.23.0", + "fbjs": "0.8.17", + "prop-types": "^15.5.8", + "relay-runtime": "1.7.0" + } + }, + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + }, + "relay-runtime": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/relay-runtime/-/relay-runtime-1.7.0.tgz", + "integrity": "sha512-gvx01aRoLHdIMQoIjMQ79js4BR9JZVfF/SoSiLXvWOgDWEnD7RKb80zmCZTByCpka0GwFzkVwBWUy1gW6g0zlQ==", + "requires": { + "babel-runtime": "^6.23.0", + "fbjs": "0.8.17" + } + }, + "ua-parser-js": { + "version": "0.7.41", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.41.tgz", + "integrity": "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==" + } + } + }, "@adobe/css-tools": { "version": "4.4.4", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", @@ -7535,6 +8176,20 @@ "@jridgewell/trace-mapping": "^0.3.24" } }, + "@apollo/client": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@apollo/client/-/client-4.0.9.tgz", + "integrity": "sha512-Lh2drMzFE9x5jVS8RKmlGL5SORkvpyUJIT+wTErxDUR2HpWePiBfhhcHHRSlZFiCR866ewCv4euTc4IDF0GWxw==", + "requires": { + "@graphql-typed-document-node/core": "^3.1.1", + "@wry/caches": "^1.0.0", + "@wry/equality": "^0.5.6", + "@wry/trie": "^0.5.0", + "graphql-tag": "^2.12.6", + "optimism": "^0.18.0", + "tslib": "^2.3.0" + } + }, "@asamuzakjp/css-color": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", @@ -8227,6 +8882,12 @@ "prop-types": "^15.8.1" } }, + "@graphql-typed-document-node/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", + "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "requires": {} + }, "@hookform/resolvers": { "version": "2.9.11", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-2.9.11.tgz", @@ -8324,6 +8985,97 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "@jumpn/utils-array": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@jumpn/utils-array/-/utils-array-0.3.4.tgz", + "integrity": "sha512-ExRwf0b0NMyMn6HLStMeqEEtmblV0BKVZ4YT3FhEJ5ErZSPN4Vv6xYUJQGNG0b7QGZJIN2KetxEoOm4MYmXygw==", + "requires": { + "babel-polyfill": "6.26.0", + "babel-runtime": "6.26.0", + "flow-static-land": "0.2.7" + } + }, + "@jumpn/utils-composite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@jumpn/utils-composite/-/utils-composite-0.7.0.tgz", + "integrity": "sha512-kamRVYJLNvjMrnKKeu2RSFQHLUO/IYFo05gLI7GQcCk063mJzsjCCfRycCievIBI+5Sg8C7A5gwRYxkBA5jY8w==", + "requires": { + "@jumpn/utils-array": "0.3.4", + "babel-polyfill": "6.26.0", + "babel-runtime": "6.26.0", + "fast-deep-equal": "1.0.0", + "flow-static-land": "0.2.8" + }, + "dependencies": { + "fast-deep-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", + "integrity": "sha512-46+Jxk9Yj/nQY+3a1KTnpbBTemcAbPySTKya8iM9D7EsiONpSWbvzesalcCJ6tmJrCUITT2fmAQfNHFG+OHM6Q==" + }, + "flow-static-land": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/flow-static-land/-/flow-static-land-0.2.8.tgz", + "integrity": "sha512-pOZFExu2rbscCgcEo7nL7FNhBubMi18dn1Un4lm8LOmQkYhgsHLsrBGMWmuJXRWcYMrOC7I/bPsiqqVjdD3K1g==" + } + } + }, + "@jumpn/utils-graphql": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jumpn/utils-graphql/-/utils-graphql-0.6.0.tgz", + "integrity": "sha512-I5OSEh8Ed4FdLIcUTYzWdpO9noQOoWptdgF8yOZ0xhDD7h7E9IgPYxfy36qbC6v9xlpGTwQMu3Wn8ulkinG/MQ==", + "requires": { + "@babel/runtime": "7.2.0", + "core-js": "2.6.0", + "graphql": "14.0.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.2.0.tgz", + "integrity": "sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg==", + "requires": { + "regenerator-runtime": "^0.12.0" + } + }, + "graphql": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.0.2.tgz", + "integrity": "sha512-gUC4YYsaiSJT1h40krG3J+USGlwhzNTXSb4IOZljn9ag5Tj+RkoXrWp+Kh7WyE3t1NCfab5kzCuxBIvOMERMXw==", + "requires": { + "iterall": "^1.2.2" + } + }, + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + } + } + }, + "@jumpn/utils-promise": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@jumpn/utils-promise/-/utils-promise-0.3.1.tgz", + "integrity": "sha512-JiaAXtcdm4PlBLToV55g0y9rFgXxoTzxW8WsKeV5T5gcVoDCA7rmISZxGjA3mv28QfdctnJO0HddAuL+gORvTQ==", + "requires": { + "@jumpn/utils-composite": "0.4.2", + "babel-polyfill": "6.26.0", + "babel-runtime": "6.26.0", + "flow-static-land": "0.2.7" + }, + "dependencies": { + "@jumpn/utils-composite": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@jumpn/utils-composite/-/utils-composite-0.4.2.tgz", + "integrity": "sha512-aDo9OyzzG62J1xylYEWRSjn6bwTjy1agEVAae1I2lZqMPp1QsLHbDoCqjfil40TpiyyYuCyZ8Zl63ByC4zIF7g==", + "requires": { + "@jumpn/utils-array": "0.3.4", + "babel-polyfill": "6.26.0", + "babel-runtime": "6.26.0", + "flow-static-land": "0.2.7" + } + } + } + }, "@monaco-editor/loader": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.6.1.tgz", @@ -9219,6 +9971,38 @@ } } }, + "@wry/caches": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@wry/caches/-/caches-1.0.1.tgz", + "integrity": "sha512-bXuaUNLVVkD20wcGBWRyo7j9N3TxePEWFZj2Y+r9OoUzfqmavM84+mFykRicNsBqatba5JLay1t48wxaXaWnlA==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@wry/context": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.7.4.tgz", + "integrity": "sha512-jmT7Sb4ZQWI5iyu3lobQxICu2nC/vbUhP0vIdd6tHC9PTfenmRmuIFqktc6GH9cgi+ZHnsLWPvfSvc4DrYmKiQ==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@wry/equality": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.5.7.tgz", + "integrity": "sha512-BRFORjsTuQv5gxcXsuDXx6oGRhuVsEGwZy6LOzRRfgu+eSfxbhUQ9L9YtSEIuIjY/o7g3iWFjrc5eSY1GXP2Dw==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@wry/trie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.5.0.tgz", + "integrity": "sha512-FNoYzHawTMk/6KMQoEG5O4PuioX19UbwdQKF44yw0nLfOypfQdjtfZzo/UIJWAJ23sNIFbD1Ug9lbaDGMwbqQA==", + "requires": { + "tslib": "^2.3.0" + } + }, "@yr/monotone-cubic-spline": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", @@ -9336,6 +10120,32 @@ "resolve": "^1.19.0" } }, + "babel-polyfill": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha512-F2rZGQnAdaHWQ8YAoeRbukc7HS9QgdgeyJ0rQDd485v9opwuPvjpPFcOOT/WmkKTdgy9ESgSPXDcTNpzrGr6iQ==", + "requires": { + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + } + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -9493,6 +10303,11 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, + "core-js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.0.tgz", + "integrity": "sha512-kLRC6ncVpuEW/1kwrOXYX6KQASCVtrh1gQr/UiaVgFlf9WE5Vp+lNe5+h3LuMr5PAucWnnEXwH0nQHRH/gpGtw==" + }, "cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -9671,6 +10486,14 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.240.tgz", "integrity": "sha512-OBwbZjWgrCOH+g6uJsA2/7Twpas2OlepS9uvByJjR2datRDuKGYeD+nP8lBBks2qnB7bGJNHDUx7c/YLaT3QMQ==" }, + "encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "requires": { + "iconv-lite": "^0.6.2" + } + }, "entities": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", @@ -10134,6 +10957,11 @@ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==" }, + "flow-static-land": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/flow-static-land/-/flow-static-land-0.2.7.tgz", + "integrity": "sha512-SmGgln4qcqLAysXM5BF8EQS+clj0TUf9+KlZUJgwuzNHvwbput+opz3CMyee9sDuLf4J/3sJbYGjdNsd2jSurA==" + }, "form-data": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", @@ -10292,6 +11120,21 @@ "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", "peer": true }, + "graphql-tag": { + "version": "2.12.6", + "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz", + "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==", + "requires": { + "tslib": "^2.1.0" + } + }, + "graphql-ws": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-6.0.6.tgz", + "integrity": "sha512-zgfER9s+ftkGKUZgc0xbx8T7/HMO4AV5/YuYiFc+AtgcO5T0v8AxYYNQ+ltzuzDZgNkYJaFspm5MMYLjQzrkmw==", + "peer": true, + "requires": {} + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -10388,7 +11231,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3.0.0" } @@ -10510,6 +11352,31 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha512-9c4TNAKYXM5PRyVcwUZrF3W09nQ+sO7+jydgs4ZGW9dhsLG2VOlISJABombdQqQRXCwuYG3sYV/puGf5rp0qmA==", + "requires": { + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" + }, + "dependencies": { + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==" + }, + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "requires": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + } + } + }, "istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -10548,6 +11415,11 @@ "istanbul-lib-report": "^3.0.0" } }, + "iterall": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", + "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==" + }, "js-cookie": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", @@ -10955,6 +11827,17 @@ "mimic-fn": "^4.0.0" } }, + "optimism": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.18.1.tgz", + "integrity": "sha512-mLXNwWPa9dgFyDqkNi54sjDyNJ9/fTI6WGBLgnXku1vdKY/jovHfZT5r+aiVeFFLOz+foPNOm5YJ4mqgld2GBQ==", + "requires": { + "@wry/caches": "^1.0.0", + "@wry/context": "^0.7.0", + "@wry/trie": "^0.5.0", + "tslib": "^2.3.0" + } + }, "optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -11049,6 +11932,12 @@ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, + "phoenix": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/phoenix/-/phoenix-1.8.1.tgz", + "integrity": "sha512-7i2a5f5y0Qhzi2g/+iuaGr4TzRt5ucqI0JeV2Q9K0swqY8L2C7CLR66RcGCpcQKGOY73B5GLKMYEl13o/9l/Cg==", + "peer": true + }, "picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -11381,6 +12270,11 @@ "strip-indent": "^3.0.0" } }, + "regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w==" + }, "relay-compiler": { "version": "17.0.0", "resolved": "https://registry.npmjs.org/relay-compiler/-/relay-compiler-17.0.0.tgz", @@ -11487,11 +12381,19 @@ "queue-microtask": "^1.2.2" } }, + "rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "peer": true, + "requires": { + "tslib": "^2.1.0" + } + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sass": { "version": "1.93.2", @@ -12014,6 +12916,11 @@ "iconv-lite": "0.6.3" } }, + "whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==" + }, "whatwg-mimetype": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", @@ -12062,7 +12969,8 @@ "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "dev": true, + "devOptional": true, + "peer": true, "requires": {} }, "xml-name-validator": { @@ -12109,6 +13017,11 @@ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==" } } + }, + "zen-observable": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.11.tgz", + "integrity": "sha512-N3xXQVr4L61rZvGMpWe8XoCGX8vhU35dPyQ4fm5CY/KDlG0F75un14hjbckPXTDuKUY6V0dqR2giT6xN8Y4GEQ==" } } } diff --git a/frontend/package.json b/frontend/package.json index b27a8671d..a65621132 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,9 @@ "license": "Apache-2.0", "type": "module", "dependencies": { + "@absinthe/socket": "^0.2.1", + "@absinthe/socket-relay": "^0.2.1", + "@apollo/client": "^4.0.9", "@formatjs/cli": "^6.2.12", "@fortawesome/fontawesome-svg-core": "^6.5.2", "@fortawesome/free-brands-svg-icons": "^6.5.2", @@ -32,10 +35,12 @@ "bootstrap": "^5.3.3", "dayjs": "^1.11.11", "graphql": "^16.9.0", + "graphql-ws": "^6.0.6", "history": "^5.1.0", "js-cookie": "^3.0.5", "leaflet": "^1.9.4", "lodash": "^4.17.21", + "phoenix": "^1.8.1", "react": "^18.3.1", "react-apexcharts": "^1.8.0", "react-bootstrap": "^2.10.4", diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 4166295b7..192a158d0 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -19,13 +19,63 @@ */ import "api/relay"; -import { Environment, Network, RecordSource, Store } from "relay-runtime"; -import type { FetchFunction, Variables, UploadableMap } from "relay-runtime"; +import { + Environment, + Network, + RecordSource, + Store, + Observable, +} from "relay-runtime"; +import type { + FetchFunction, + Variables, + UploadableMap, + RequestParameters, + SubscribeFunction, +} from "relay-runtime"; import type { TaskScheduler } from "relay-runtime"; import ReactDOM from "react-dom"; import type { Session } from "contexts/Session"; +// Phoenix WebSocket V2 message format types +type PhoenixJoinRef = string | null; +type PhoenixRef = string | null; +type PhoenixTopic = string; +type PhoenixEvent = + | "phx_join" + | "phx_leave" + | "phx_reply" + | "phx_close" + | "phx_error" + | "heartbeat" + | "doc" + | "subscription:data"; + +interface PhoenixPayload { + status?: "ok" | "error"; + response?: { + subscriptionId?: string; + errors?: Array<{ message: string; locations?: unknown[]; path?: string[] }>; + [key: string]: unknown; + }; + result?: { + data?: unknown; + errors?: Array<{ message: string; locations?: unknown[]; path?: string[] }>; + }; + subscriptionId?: string; + [key: string]: unknown; +} + +// Phoenix V2 WebSocket message: [join_ref, ref, topic, event, payload] +type PhoenixMessage = [ + PhoenixJoinRef, + PhoenixRef, + PhoenixTopic, + PhoenixEvent, + PhoenixPayload, +]; + const applicationMetatag: HTMLElement = document.head.querySelector( "[name=application-name]", )!; @@ -132,6 +182,233 @@ const relayScheduler: TaskScheduler = { }, }; +const createSubscribeFunction = (session: Session): SubscribeFunction => { + return (operation: RequestParameters, variables: Variables) => { + return Observable.create((sink) => { + if (!session) { + sink.error(new Error("Session is null")); + return; + } + + const endpoints = [ + `socket/websocket?vsn=2.0.0`, + `tenants/${session.tenantSlug}/socket/websocket?vsn=2.0.0`, + `api/socket/websocket?vsn=2.0.0`, + ]; + + const wsProtocol = backendUrl.startsWith("https") ? "wss" : "ws"; + const wsBaseUrl = backendUrl.replace(/^https?:/, wsProtocol + ":"); + + const wsUrl = new URL(endpoints[0], wsBaseUrl).toString(); + + console.log("Connecting to WebSocket:", wsUrl); + + const socket = new WebSocket(wsUrl); + const subscriptionId = Math.random().toString(36).substring(7); + let channelJoined = false; + let subscriptionTopic: string | null = null; + let heartbeatInterval: number | null = null; + + socket.onopen = () => { + console.log("WebSocket opened successfully"); + + // Start heartbeat - Phoenix V2 format: [join_ref, ref, topic, event, payload] + heartbeatInterval = setInterval(() => { + if (socket.readyState === WebSocket.OPEN) { + socket.send( + JSON.stringify([ + null, + Date.now().toString(), + "phoenix", + "heartbeat", + {}, + ]), + ); + } + }, 30000); + + // Join the __absinthe__:control channel - Phoenix V2 format + socket.send( + JSON.stringify([null, "1", "__absinthe__:control", "phx_join", {}]), + ); + }; + + socket.onmessage = (event) => { + const message: PhoenixMessage = JSON.parse(event.data); + console.log("WebSocket message received:", message); + + // Phoenix V2 format: [join_ref, ref, topic, event, payload] + const [, ref, topic, eventName, payload] = message; + + // Handle heartbeat responses + if ( + eventName === "phx_reply" && + ref && + ref !== "1" && + ref !== subscriptionId + ) { + return; + } + + // Handle channel join reply + if ( + topic === "__absinthe__:control" && + eventName === "phx_reply" && + ref === "1" + ) { + if (payload.status === "ok") { + console.log("Channel joined successfully"); + channelJoined = true; + + // Send the subscription request + socket.send( + JSON.stringify([ + null, + subscriptionId, + "__absinthe__:control", + "doc", + { + query: operation.text, + variables: variables, + }, + ]), + ); + } else { + console.error("Channel join failed:", payload); + const errorMsg = payload.response?.errors + ? JSON.stringify(payload.response.errors) + : "Channel join failed"; + sink.error(new Error(errorMsg)); + } + } + + // Handle subscription response + if (ref === subscriptionId && eventName === "phx_reply") { + if (payload.status === "ok") { + subscriptionTopic = payload.response?.subscriptionId ?? null; + console.log("Subscription created:", subscriptionTopic); + + if (subscriptionTopic) { + socket.send( + JSON.stringify([ + null, + subscriptionId + "_join", + subscriptionTopic, + "phx_join", + {}, + ]), + ); + } + } else { + console.error("Subscription failed:", payload); + const errorMsg = payload.response?.errors + ? JSON.stringify(payload.response.errors) + : "Subscription failed"; + sink.error(new Error(errorMsg)); + } + } + + // Handle subscription data + if (eventName === "subscription:data") { + console.log("Subscription data received:", payload); + const data = payload.result; + if (data?.errors) { + sink.error(new Error(JSON.stringify(data.errors))); + } else if (data?.data) { + sink.next({ + data: data.data as Record, + errors: [], + }); + } + } + + // Handle completion + if (eventName === "phx_close") { + console.log("Subscription closed by server"); + sink.complete(); + } + }; + + socket.onerror = (error) => { + console.error("WebSocket error occurred:", error); + sink.error(new Error("WebSocket connection error")); + }; + + socket.onclose = (event) => { + console.log("WebSocket closed:", { + code: event.code, + reason: event.reason, + wasClean: event.wasClean, + url: wsUrl, + }); + + if (heartbeatInterval) { + clearInterval(heartbeatInterval); + } + + const closeReasons: Record = { + 1000: "Normal closure", + 1006: "Abnormal closure - connection lost before handshake", + 1008: "Policy violation - likely authentication failure", + 1011: "Server error - backend rejected the connection", + }; + + const reason = + closeReasons[event.code] || `Unknown close code ${event.code}`; + console.log(`Close reason: ${reason}`); + + if (!event.wasClean && event.code !== 1000) { + const errorMsg = event.reason || reason; + sink.error(new Error(errorMsg)); + } else { + sink.complete(); + } + }; + + return () => { + console.log("Cleaning up WebSocket subscription"); + + if (heartbeatInterval) { + clearInterval(heartbeatInterval); + } + + if (socket.readyState === WebSocket.OPEN) { + if (subscriptionTopic) { + socket.send( + JSON.stringify([ + null, + subscriptionId + "_leave_sub", + subscriptionTopic, + "phx_leave", + {}, + ]), + ); + } + + if (channelJoined) { + socket.send( + JSON.stringify([ + null, + subscriptionId + "_leave", + "__absinthe__:control", + "phx_leave", + {}, + ]), + ); + } + } + + if ( + socket.readyState !== WebSocket.CLOSED && + socket.readyState !== WebSocket.CLOSING + ) { + socket.close(1000, "Client closing subscription"); + } + }; + }); + }; +}; + const relayEnvironment = (session: Session) => { const fetchRelay: FetchFunction = async ( operation, @@ -160,8 +437,10 @@ const relayEnvironment = (session: Session) => { ); }; + const subscribeRelay = createSubscribeFunction(session); + return new Environment({ - network: Network.create(fetchRelay), + network: Network.create(fetchRelay, subscribeRelay), store: new Store(new RecordSource()), scheduler: relayScheduler, }); diff --git a/frontend/src/api/schema.graphql b/frontend/src/api/schema.graphql index 8e111d53a..d69dd0736 100644 --- a/frontend/src/api/schema.graphql +++ b/frontend/src/api/schema.graphql @@ -1,4 +1,5 @@ schema { + subscription: RootSubscriptionType mutation: RootMutationType query: RootQueryType } @@ -2422,6 +2423,54 @@ type DeploymentEvent { updatedAt: DateTime! } +type deployment_state_changed_result { + updated: Deployment +} + +type deployment_upgraded_result { + updated: Deployment +} + +type deployment_event_added_result { + updated: Deployment +} + +type deployment_timed_out_result { + updated: Deployment +} + +type deployment_deleted_action_result { + updated: Deployment +} + +type deployment_stopped_result { + updated: Deployment +} + +type deployment_started_result { + updated: Deployment +} + +type deployment_sent_result { + updated: Deployment +} + +type deployment_deployed_result { + created: Deployment +} + +type deployment_deleted_result { + destroyed: ID +} + +type deployment_updated_result { + updated: Deployment +} + +type deployment_created_result { + created: Deployment +} + "The result of the :upgrade_deployment mutation" type UpgradeDeploymentResult { "The successful result of the mutation" @@ -3965,6 +4014,30 @@ type HardwareType implements Node { ): HardwareTypePartNumberConnection! } +type device_deleted_result { + destroyed: ID +} + +type device_tags_updated_result { + updated: Device +} + +type device_disconnected_result { + created: Device +} + +type device_connected_result { + created: Device +} + +type device_updated_result { + updated: Device +} + +type device_created_result { + created: Device +} + "The result of the :set_device_led_behavior mutation" type SetDeviceLedBehaviorResult { "The successful result of the mutation" @@ -6491,6 +6564,81 @@ type RootMutationType { deleteBaseImageCollection(id: ID!): DeleteBaseImageCollectionResult } +type RootSubscriptionType { + deviceCreated( + "A filter to limit the results" + filter: DeviceFilterInput + ): device_created_result + deviceUpdated( + "A filter to limit the results" + filter: DeviceFilterInput + ): device_updated_result + deviceConnected( + "A filter to limit the results" + filter: DeviceFilterInput + ): device_connected_result + deviceDisconnected( + "A filter to limit the results" + filter: DeviceFilterInput + ): device_disconnected_result + deviceTagsUpdated( + "A filter to limit the results" + filter: DeviceFilterInput + ): device_tags_updated_result + deviceDeleted( + "A filter to limit the results" + filter: DeviceFilterInput + ): device_deleted_result + deploymentCreated( + "A filter to limit the results" + filter: DeploymentFilterInput + ): deployment_created_result + deploymentUpdated( + "A filter to limit the results" + filter: DeploymentFilterInput + ): deployment_updated_result + deploymentDeleted( + "A filter to limit the results" + filter: DeploymentFilterInput + ): deployment_deleted_result + deploymentDeployed( + "A filter to limit the results" + filter: DeploymentFilterInput + ): deployment_deployed_result + deploymentSent( + "A filter to limit the results" + filter: DeploymentFilterInput + ): deployment_sent_result + deploymentStarted( + "A filter to limit the results" + filter: DeploymentFilterInput + ): deployment_started_result + deploymentStopped( + "A filter to limit the results" + filter: DeploymentFilterInput + ): deployment_stopped_result + deploymentDeletedAction( + "A filter to limit the results" + filter: DeploymentFilterInput + ): deployment_deleted_action_result + deploymentTimedOut( + "A filter to limit the results" + filter: DeploymentFilterInput + ): deployment_timed_out_result + deploymentEventAdded( + "A filter to limit the results" + filter: DeploymentFilterInput + ): deployment_event_added_result + deploymentUpgraded( + "A filter to limit the results" + filter: DeploymentFilterInput + ): deployment_upgraded_result + deploymentStateChanged( + "A filter to limit the results" + filter: DeploymentFilterInput + ): deployment_state_changed_result +} + """ The `Json` scalar type represents arbitrary json string data, represented as UTF-8 character sequences. The Json type is most often used to represent a free-form diff --git a/frontend/src/components/DevicesTable.tsx b/frontend/src/components/DevicesTable.tsx index 05350b60a..2c6ecab21 100644 --- a/frontend/src/components/DevicesTable.tsx +++ b/frontend/src/components/DevicesTable.tsx @@ -20,7 +20,13 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { FormattedMessage } from "react-intl"; -import { graphql, usePaginationFragment } from "react-relay/hooks"; +import { + graphql, + usePaginationFragment, + useSubscription, +} from "react-relay/hooks"; +import { ConnectionHandler, commitLocalUpdate } from "relay-runtime"; +import { useRelayEnvironment } from "react-relay"; import _ from "lodash"; import type { DevicesTable_PaginationQuery } from "api/__generated__/DevicesTable_PaginationQuery.graphql"; @@ -72,6 +78,37 @@ const DEVICES_TABLE_FRAGMENT = graphql` } `; +const DEVICE_CREATED_SUBSCRIPTION = graphql` + subscription DevicesTable_deviceCreated_Subscription { + deviceCreated { + created { + id + deviceId + name + online + lastConnection + lastDisconnection + systemModel { + id + name + hardwareType { + id + name + } + } + tags { + edges { + node { + id + name + } + } + } + } + } + } +`; + type TableRecord = NonNullable< NonNullable["edges"] >[number]["node"]; @@ -192,8 +229,54 @@ const DevicesTable = ({ className, devicesRef }: Props) => { DevicesTable_DeviceFragment$key >(DEVICES_TABLE_FRAGMENT, devicesRef); + const relayEnvironment = useRelayEnvironment(); const [searchText, setSearchText] = useState(null); + useSubscription( + useMemo( + () => ({ + subscription: DEVICE_CREATED_SUBSCRIPTION, + variables: {}, + onNext: (data: any) => { + const created = data.deviceCreated?.created; + if (created) { + if (!searchText) { + refetch( + { + first: RECORDS_TO_LOAD_FIRST, + }, + { fetchPolicy: "store-and-network" }, + ); + } + + const updater = (store: any) => { + const newDevice = store.get(created.id); + if (newDevice) { + const root = store.getRoot(); + const connection = ConnectionHandler.getConnection( + root, + "DevicesTable_devices", + ); + if (connection) { + const edge = ConnectionHandler.createEdge( + store, + connection, + newDevice, + "DeviceEdge", + ); + ConnectionHandler.insertEdgeBefore(connection, edge); + } + } + }; + + commitLocalUpdate(relayEnvironment, updater); + } + }, + }), + [refetch, searchText, relayEnvironment], + ), + ); + const debounceRefetch = useMemo( () => _.debounce((text: string) => { diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index ce78fdc50..cbaf61117 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -13,6 +13,9 @@ export default defineConfig((env) => { build: { outDir: "build", }, + optimizeDeps: { + exclude: ["@apollo/client"], + }, plugins: [ react(), viteTsconfigPaths(),