signalk-edge-link 2.2.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,164 +1,164 @@
1
- {
2
- "name": "signalk-edge-link",
3
- "version": "2.2.0",
4
- "description": "SignalK Edge Link. Secure UDP link for data exchange.",
5
- "main": "lib/index.js",
6
- "files": [
7
- "lib/",
8
- "public/"
9
- ],
10
- "keywords": [
11
- "signalk-node-server-plugin",
12
- "signalk-category-network",
13
- "signalk-webapp",
14
- "signalk-category-utility",
15
- "signalk-plugin-configurator"
16
- ],
17
- "signalk": {
18
- "appIcon": "./icons/icon-72x72.png",
19
- "displayName": "Edge Link Configuration"
20
- },
21
- "signalk-plugin-enabled-by-default": false,
22
- "scripts": {
23
- "clean:lib": "node -e \"const fs=require('fs');if(fs.existsSync('lib'))fs.rmSync('lib',{recursive:true,force:true});\"",
24
- "clean:public": "node -e \"const fs=require('fs');if(fs.existsSync('public'))fs.rmSync('public',{recursive:true,force:true});\"",
25
- "build": "npm run build:ts && npm run build:web",
26
- "build:web": "npm run clean:public && webpack --mode production",
27
- "build:ts": "npm run clean:lib && tsc",
28
- "check:ts": "tsc --noEmit",
29
- "dev": "webpack --mode development --watch",
30
- "test": "jest --runInBand",
31
- "test:v2": "jest __tests__/v2/",
32
- "test:integration": "jest test/integration/",
33
- "test:watch": "jest --watch",
34
- "test:coverage": "jest --coverage",
35
- "lint": "eslint .",
36
- "lint:fix": "eslint . --fix",
37
- "format": "prettier --write \"**/*.{js,ts,json,md}\"",
38
- "migrate:config": "node lib/scripts/migrate-config.js",
39
- "cli": "node lib/bin/edge-link-cli.js",
40
- "prepare": "husky"
41
- },
42
- "dependencies": {
43
- "@msgpack/msgpack": "^3.0.0",
44
- "@rjsf/core": "^5.18.4",
45
- "@rjsf/utils": "^5.18.4",
46
- "@rjsf/validator-ajv8": "^5.18.4",
47
- "ping-monitor": "^0.8.2"
48
- },
49
- "devDependencies": {
50
- "@babel/core": "^7.22.0",
51
- "@babel/preset-env": "^7.22.0",
52
- "@babel/preset-react": "^7.22.0",
53
- "@testing-library/jest-dom": "^5.17.0",
54
- "@testing-library/react": "^12.1.5",
55
- "@types/node": "^25.3.5",
56
- "@types/react": "^16.14.0",
57
- "@types/react-dom": "^16.9.0",
58
- "babel-loader": "^9.1.2",
59
- "copy-webpack-plugin": "^14.0.0",
60
- "css-loader": "^6.8.1",
61
- "eslint": "^8.57.1",
62
- "eslint-plugin-react": "^7.37.5",
63
- "html-webpack-plugin": "^5.5.3",
64
- "husky": "^9.1.7",
65
- "jest": "^29.7.0",
66
- "jest-environment-jsdom": "^30.3.0",
67
- "lint-staged": "^15.4.3",
68
- "mini-css-extract-plugin": "^2.7.6",
69
- "prettier": "^3.6.2",
70
- "react": "^16.13.1",
71
- "react-dom": "^16.13.1",
72
- "react-test-renderer": "^16.14.0",
73
- "style-loader": "^3.3.3",
74
- "ts-jest": "^29.4.6",
75
- "ts-loader": "^9.5.4",
76
- "typescript": "^5.9.3",
77
- "webpack": "^5.102.1",
78
- "webpack-cli": "^5.1.4"
79
- },
80
- "engines": {
81
- "node": ">=16"
82
- },
83
- "author": "Karl-Erik Gustafsson",
84
- "repository": "https://github.com/KEGustafsson/signalk-edge-link",
85
- "homepage": "https://github.com/KEGustafsson/signalk-edge-link#readme",
86
- "bugs": {
87
- "url": "https://github.com/KEGustafsson/signalk-edge-link/issues"
88
- },
89
- "license": "MIT",
90
- "jest": {
91
- "testEnvironment": "node",
92
- "coverageDirectory": "coverage",
93
- "collectCoverageFrom": [
94
- "lib/**/*.js",
95
- "!lib/webapp/**",
96
- "!lib/components/**",
97
- "!lib/utils/**"
98
- ],
99
- "testMatch": [
100
- "**/__tests__/**/*.js",
101
- "**/*.test.js",
102
- "**/*.spec.js"
103
- ],
104
- "transform": {
105
- "^.+\\.js$": [
106
- "babel-jest",
107
- {
108
- "presets": [
109
- [
110
- "@babel/preset-env",
111
- {
112
- "targets": {
113
- "node": "current"
114
- }
115
- }
116
- ]
117
- ]
118
- }
119
- ],
120
- ".+\\.tsx$": [
121
- "ts-jest",
122
- {
123
- "tsconfig": "tsconfig.webapp.json",
124
- "diagnostics": false
125
- }
126
- ],
127
- "^.+\\.ts$": "ts-jest"
128
- },
129
- "moduleFileExtensions": [
130
- "ts",
131
- "tsx",
132
- "js",
133
- "json",
134
- "node"
135
- ],
136
- "testPathIgnorePatterns": [
137
- "/node_modules/",
138
- "/public/"
139
- ],
140
- "coverageThreshold": {
141
- "global": {
142
- "branches": 60,
143
- "functions": 65,
144
- "lines": 65,
145
- "statements": 65
146
- }
147
- }
148
- },
149
- "bin": {
150
- "edge-link-cli": "lib/bin/edge-link-cli.js"
151
- },
152
- "lint-staged": {
153
- "*.js": [
154
- "prettier --write",
155
- "eslint --fix"
156
- ],
157
- "*.ts": [
158
- "prettier --write"
159
- ],
160
- "*.{json,md}": [
161
- "prettier --write"
162
- ]
163
- }
164
- }
1
+ {
2
+ "name": "signalk-edge-link",
3
+ "version": "2.2.1",
4
+ "description": "SignalK Edge Link. Secure UDP link for data exchange.",
5
+ "main": "lib/index.js",
6
+ "files": [
7
+ "lib/",
8
+ "public/"
9
+ ],
10
+ "keywords": [
11
+ "signalk-node-server-plugin",
12
+ "signalk-category-network",
13
+ "signalk-webapp",
14
+ "signalk-category-utility",
15
+ "signalk-plugin-configurator"
16
+ ],
17
+ "signalk": {
18
+ "appIcon": "./icons/icon-72x72.png",
19
+ "displayName": "Edge Link Configuration"
20
+ },
21
+ "signalk-plugin-enabled-by-default": false,
22
+ "scripts": {
23
+ "clean:lib": "node -e \"const fs=require('fs');if(fs.existsSync('lib'))fs.rmSync('lib',{recursive:true,force:true});\"",
24
+ "clean:public": "node -e \"const fs=require('fs');if(fs.existsSync('public'))fs.rmSync('public',{recursive:true,force:true});\"",
25
+ "build": "npm run build:ts && npm run build:web",
26
+ "build:web": "npm run clean:public && webpack --mode production",
27
+ "build:ts": "npm run clean:lib && tsc",
28
+ "check:ts": "tsc --noEmit",
29
+ "dev": "webpack --mode development --watch",
30
+ "test": "jest --runInBand",
31
+ "test:v2": "jest __tests__/v2/",
32
+ "test:integration": "jest test/integration/",
33
+ "test:watch": "jest --watch",
34
+ "test:coverage": "jest --coverage",
35
+ "lint": "eslint .",
36
+ "lint:fix": "eslint . --fix",
37
+ "format": "prettier --write \"**/*.{js,ts,json,md}\"",
38
+ "migrate:config": "node lib/scripts/migrate-config.js",
39
+ "cli": "node lib/bin/edge-link-cli.js",
40
+ "prepare": "husky"
41
+ },
42
+ "dependencies": {
43
+ "@msgpack/msgpack": "^3.0.0",
44
+ "@rjsf/core": "^5.18.4",
45
+ "@rjsf/utils": "^5.18.4",
46
+ "@rjsf/validator-ajv8": "^5.18.4",
47
+ "ping-monitor": "^0.8.2"
48
+ },
49
+ "devDependencies": {
50
+ "@babel/core": "^7.22.0",
51
+ "@babel/preset-env": "^7.22.0",
52
+ "@babel/preset-react": "^7.22.0",
53
+ "@testing-library/jest-dom": "^5.17.0",
54
+ "@testing-library/react": "^12.1.5",
55
+ "@types/node": "^25.3.5",
56
+ "@types/react": "^16.14.0",
57
+ "@types/react-dom": "^16.9.0",
58
+ "babel-loader": "^9.1.2",
59
+ "copy-webpack-plugin": "^14.0.0",
60
+ "css-loader": "^6.8.1",
61
+ "eslint": "^8.57.1",
62
+ "eslint-plugin-react": "^7.37.5",
63
+ "html-webpack-plugin": "^5.5.3",
64
+ "husky": "^9.1.7",
65
+ "jest": "^29.7.0",
66
+ "jest-environment-jsdom": "^30.3.0",
67
+ "lint-staged": "^15.4.3",
68
+ "mini-css-extract-plugin": "^2.7.6",
69
+ "prettier": "^3.6.2",
70
+ "react": "^16.13.1",
71
+ "react-dom": "^16.13.1",
72
+ "react-test-renderer": "^16.14.0",
73
+ "style-loader": "^3.3.3",
74
+ "ts-jest": "^29.4.6",
75
+ "ts-loader": "^9.5.4",
76
+ "typescript": "^5.9.3",
77
+ "webpack": "^5.102.1",
78
+ "webpack-cli": "^5.1.4"
79
+ },
80
+ "engines": {
81
+ "node": ">=16"
82
+ },
83
+ "author": "Karl-Erik Gustafsson",
84
+ "repository": "https://github.com/KEGustafsson/signalk-edge-link",
85
+ "homepage": "https://github.com/KEGustafsson/signalk-edge-link#readme",
86
+ "bugs": {
87
+ "url": "https://github.com/KEGustafsson/signalk-edge-link/issues"
88
+ },
89
+ "license": "MIT",
90
+ "jest": {
91
+ "testEnvironment": "node",
92
+ "coverageDirectory": "coverage",
93
+ "collectCoverageFrom": [
94
+ "lib/**/*.js",
95
+ "!lib/webapp/**",
96
+ "!lib/components/**",
97
+ "!lib/utils/**"
98
+ ],
99
+ "testMatch": [
100
+ "**/__tests__/**/*.js",
101
+ "**/*.test.js",
102
+ "**/*.spec.js"
103
+ ],
104
+ "transform": {
105
+ "^.+\\.js$": [
106
+ "babel-jest",
107
+ {
108
+ "presets": [
109
+ [
110
+ "@babel/preset-env",
111
+ {
112
+ "targets": {
113
+ "node": "current"
114
+ }
115
+ }
116
+ ]
117
+ ]
118
+ }
119
+ ],
120
+ ".+\\.tsx$": [
121
+ "ts-jest",
122
+ {
123
+ "tsconfig": "tsconfig.webapp.json",
124
+ "diagnostics": false
125
+ }
126
+ ],
127
+ "^.+\\.ts$": "ts-jest"
128
+ },
129
+ "moduleFileExtensions": [
130
+ "ts",
131
+ "tsx",
132
+ "js",
133
+ "json",
134
+ "node"
135
+ ],
136
+ "testPathIgnorePatterns": [
137
+ "/node_modules/",
138
+ "/public/"
139
+ ],
140
+ "coverageThreshold": {
141
+ "global": {
142
+ "branches": 60,
143
+ "functions": 65,
144
+ "lines": 65,
145
+ "statements": 65
146
+ }
147
+ }
148
+ },
149
+ "bin": {
150
+ "edge-link-cli": "lib/bin/edge-link-cli.js"
151
+ },
152
+ "lint-staged": {
153
+ "*.js": [
154
+ "prettier --write",
155
+ "eslint --fix"
156
+ ],
157
+ "*.ts": [
158
+ "prettier --write"
159
+ ],
160
+ "*.{json,md}": [
161
+ "prettier --write"
162
+ ]
163
+ }
164
+ }
@@ -1,2 +1,2 @@
1
- "use strict";(self.webpackChunksignalk_edge_link=self.webpackChunksignalk_edge_link||[]).push([[982],{4353(e,t,n){n.r(t),n.d(t,{default:()=>K});var r=n(4147),i=n(6718),o=n(4810),a=n(7936);const s="Management token required/invalid.",l={token:null,localStorageKey:"signalkEdgeLinkManagementToken",queryParam:"edgeLinkToken",includeTokenInQuery:!1,headerMode:"both"};function c(e,t={}){const n=function(){if("undefined"==typeof window)return l;const e=window.__EDGE_LINK_AUTH__;return e&&"object"==typeof e?{...l,...e}:l}(),r=function(e){if(e.token)return String(e.token).trim();if("undefined"==typeof window)return"";if(e.includeTokenInQuery&&e.queryParam){const t=new URLSearchParams(window.location.search).get(e.queryParam);if(t)return t.trim()}if(e.localStorageKey&&window.localStorage){const t=window.localStorage.getItem(e.localStorageKey);if(t)return t.trim()}return""}(n),i=new Headers(t.headers||{});return function(e,t,n){if(!t)return e;const r=(n||"both").toLowerCase();"x-edge-link-token"!==r&&"token"!==r&&"both"!==r||e.set("X-Edge-Link-Token",t),"authorization"!==r&&"bearer"!==r&&"both"!==r||e.set("Authorization",`Bearer ${t}`)}(i,r,n.headerMode),fetch(e,{...t,headers:i})}const d={name:{type:"string",title:"Connection Name",description:"Human-readable label for this connection (e.g. 'Shore Server', 'Sat Client'). Used to namespace config files and Signal K metrics paths.",default:"connection",maxLength:40},serverType:{type:"string",title:"Operation Mode",description:"Select Server to receive data, or Client to send data.",default:"client",oneOf:[{const:"server",title:"Server Mode – Receive Data"},{const:"client",title:"Client Mode – Send Data"}]},udpPort:{type:"number",title:"UDP Port",description:"UDP port for data transmission (must match on both ends).",default:4446,minimum:1024,maximum:65535},secretKey:{type:"string",title:"Encryption Key",description:"32-byte secret key: 32-character ASCII, 64-character hex, or 44-character base64.",minLength:32,maxLength:64,pattern:"^(?:.{32}|[0-9a-fA-F]{64}|[A-Za-z0-9+/]{43}=?)$"},stretchAsciiKey:{type:"boolean",title:"Stretch 32-char ASCII Key (PBKDF2)",description:`When the secretKey is 32-character ASCII, route it through PBKDF2-SHA256 (${6e5.toLocaleString("en-US")} iterations) to raise it to full 256-bit AES strength. Hex and base64 keys are unaffected. BOTH ENDS OF THE CONNECTION MUST USE THE SAME SETTING — otherwise authentication will fail and every packet will be dropped.`,default:!1},useMsgpack:{type:"boolean",title:"Use MessagePack",description:"Binary serialization for smaller payloads (must match on both ends).",default:!1},usePathDictionary:{type:"boolean",title:"Use Path Dictionary",description:"Encode paths as numeric IDs for bandwidth savings (must match on both ends).",default:!1},protocolVersion:{type:"number",title:"Protocol Version",description:"v1: encrypted UDP. v2 adds reliable delivery and metrics. v3 keeps the v2 data path and authenticates control packets (ACK/NAK/HEARTBEAT/HELLO). Must match on both ends.",default:1,oneOf:[{const:1,title:"v1 – Standard encrypted UDP"},{const:2,title:"v2 – Reliability, congestion control, bonding, metrics"},{const:3,title:"v3 - v2 features with authenticated control packets"}]}},m={udpAddress:{type:"string",title:"Server Address",description:"IP address or hostname of the remote Signal K endpoint.",default:"127.0.0.1"},helloMessageSender:{type:"integer",title:"Heartbeat Interval (seconds)",description:"Send periodic heartbeat messages to keep NAT/firewall mappings alive.",default:60,minimum:10,maximum:3600},testAddress:{type:"string",title:"Connectivity Test Address",description:"Host used for reachability checks (e.g. 8.8.8.8).",default:"127.0.0.1"},testPort:{type:"number",title:"Connectivity Test Port",description:"Port used for reachability checks (e.g. 53, 80, or 443).",default:80,minimum:1,maximum:65535},pingIntervalTime:{type:"number",title:"Check Interval (minutes)",description:"Frequency of network reachability checks.",default:1,minimum:.1,maximum:60},heartbeatInterval:{type:"number",title:"NAT Keepalive Heartbeat Interval (ms)",description:"v2/v3 only. How often to send UDP heartbeat packets for NAT traversal. Typical NAT timeouts range from 30s to 120s.",default:25e3,minimum:5e3,maximum:12e4}},u={type:"object",title:"Reliability Settings (v2/v3 only)",description:"Requires Protocol v2 or v3. Controls retransmit queue behavior and packet retry limits.",properties:{retransmitQueueSize:{type:"number",title:"Retransmit Queue Size",description:"Maximum number of sent packets stored for potential retransmission.",default:5e3,minimum:100,maximum:5e4},maxRetransmits:{type:"number",title:"Max Retransmit Attempts",description:"Maximum resend attempts before a packet is dropped from the retransmit queue.",default:3,minimum:1,maximum:20},retransmitMaxAge:{type:"number",title:"Retransmit Max Age (ms)",description:"Expire stale unacknowledged packets older than this age.",default:12e4,minimum:1e3,maximum:3e5},retransmitMinAge:{type:"number",title:"Retransmit Min Age (ms)",description:"Minimum packet age before expiration is allowed.",default:1e4,minimum:200,maximum:3e4},retransmitRttMultiplier:{type:"number",title:"RTT Expiry Multiplier",description:"Dynamic expiry age = RTT × this multiplier.",default:12,minimum:2,maximum:20},ackIdleDrainAge:{type:"number",title:"ACK Idle Drain Age (ms)",description:"If ACKs are idle longer than this, expiry becomes more aggressive.",default:2e4,minimum:500,maximum:3e4},forceDrainAfterAckIdle:{type:"boolean",title:"Force Drain After ACK Idle",description:"When enabled, clear retransmit queue if no ACKs arrive for too long.",default:!1},forceDrainAfterMs:{type:"number",title:"Force Drain Timeout (ms)",description:"ACK idle duration before force-draining retransmit queue to zero.",default:45e3,minimum:2e3,maximum:12e4},recoveryBurstEnabled:{type:"boolean",title:"Recovery Burst Enabled",description:"When ACKs return after outage, rapidly retransmit queued packets to catch up.",default:!0},recoveryBurstSize:{type:"number",title:"Recovery Burst Size",description:"Max queued packets to retransmit per recovery burst cycle.",default:100,minimum:10,maximum:1e3},recoveryBurstIntervalMs:{type:"number",title:"Recovery Burst Interval (ms)",description:"Interval between recovery burst cycles while backlog exists.",default:200,minimum:50,maximum:5e3},recoveryAckGapMs:{type:"number",title:"Recovery ACK Gap (ms)",description:"Minimum ACK silence before triggering fast recovery bursts.",default:4e3,minimum:500,maximum:12e4}}},p={type:"object",title:"Reliability Settings (v2/v3 only)",description:"Requires Protocol v2 or v3. Controls ACK/NAK timing for reliable delivery.",properties:{ackInterval:{type:"number",title:"ACK Interval (ms)",description:"How often server sends cumulative ACK updates.",default:100,minimum:20,maximum:5e3},ackResendInterval:{type:"number",title:"ACK Resend Interval (ms)",description:"Re-send duplicate ACK periodically to recover from lost ACK packets.",default:1e3,minimum:100,maximum:1e4},nakTimeout:{type:"number",title:"NAK Timeout (ms)",description:"Delay before requesting retransmission for missing sequence numbers.",default:100,minimum:20,maximum:5e3}}},g={type:"object",title:"Dynamic Congestion Control (v2/v3 only)",description:"Requires Protocol v2 or v3. AIMD algorithm to dynamically adjust send rate based on network conditions.",properties:{enabled:{type:"boolean",title:"Enable Congestion Control",description:"Automatically adjust delta timer based on RTT and packet loss.",default:!1},targetRTT:{type:"number",title:"Target RTT (ms)",description:"RTT threshold above which send rate is reduced.",default:200,minimum:50,maximum:2e3},nominalDeltaTimer:{type:"number",title:"Nominal Delta Timer (ms)",description:"Preferred steady-state send interval.",default:1e3,minimum:100,maximum:1e4},minDeltaTimer:{type:"number",title:"Minimum Delta Timer (ms)",description:"Fastest allowed send interval.",default:100,minimum:50,maximum:1e3},maxDeltaTimer:{type:"number",title:"Maximum Delta Timer (ms)",description:"Slowest allowed send interval.",default:5e3,minimum:1e3,maximum:3e4}}},f={type:"object",title:"Connection Bonding (v2/v3 only)",description:"Requires Protocol v2 or v3. Dual-link bonding with automatic failover between primary and backup connections.",properties:{enabled:{type:"boolean",title:"Enable Connection Bonding",description:"Enable dual-link bonding with automatic failover.",default:!1},mode:{type:"string",title:"Bonding Mode",description:"Bonding operating mode.",default:"main-backup",oneOf:[{const:"main-backup",title:"Main/Backup – Failover to backup when primary degrades"}]},primary:{type:"object",title:"Primary Link",description:"Primary connection (e.g. LTE modem).",properties:{address:{type:"string",title:"Server Address",default:"127.0.0.1"},port:{type:"number",title:"UDP Port",default:4446,minimum:1024,maximum:65535},interface:{type:"string",title:"Bind Interface (optional)",description:"Network interface IP to bind to."}}},backup:{type:"object",title:"Backup Link",description:"Backup connection (e.g. Starlink, satellite).",properties:{address:{type:"string",title:"Server Address",default:"127.0.0.1"},port:{type:"number",title:"UDP Port",default:4447,minimum:1024,maximum:65535},interface:{type:"string",title:"Bind Interface (optional)",description:"Network interface IP to bind to."}}},failover:{type:"object",title:"Failover Thresholds",description:"Configure when failover is triggered.",properties:{rttThreshold:{type:"number",title:"RTT Threshold (ms)",default:500,minimum:100,maximum:5e3},lossThreshold:{type:"number",title:"Packet Loss Threshold (0-1)",default:.1,minimum:.01,maximum:.5},healthCheckInterval:{type:"number",title:"Health Check Interval (ms)",default:1e3,minimum:500,maximum:1e4},failbackDelay:{type:"number",title:"Failback Delay (ms)",default:3e4,minimum:5e3,maximum:3e5},heartbeatTimeout:{type:"number",title:"Heartbeat Timeout (ms)",default:5e3,minimum:1e3,maximum:3e4}}}}},y={type:"boolean",title:"Enable Signal K Notifications",description:"Emit Signal K notifications for alerts and failover events.",default:!1},b={type:"object",title:"Monitoring Alert Thresholds (v2/v3 only)",description:"Customize warning/critical thresholds for network monitoring alerts.",properties:{rtt:{type:"object",title:"RTT Thresholds",properties:{warning:{type:"number",title:"Warning RTT (ms)",default:300},critical:{type:"number",title:"Critical RTT (ms)",default:800}}},packetLoss:{type:"object",title:"Packet Loss Thresholds",properties:{warning:{type:"number",title:"Warning Loss Ratio",default:.03},critical:{type:"number",title:"Critical Loss Ratio",default:.1}}},retransmitRate:{type:"object",title:"Retransmit Rate Thresholds",properties:{warning:{type:"number",title:"Warning Retransmit Ratio",default:.05},critical:{type:"number",title:"Critical Retransmit Ratio",default:.15}}},jitter:{type:"object",title:"Jitter Thresholds",properties:{warning:{type:"number",title:"Warning Jitter (ms)",default:100},critical:{type:"number",title:"Critical Jitter (ms)",default:300}}},queueDepth:{type:"object",title:"Queue Depth Thresholds",properties:{warning:{type:"number",title:"Warning Queue Depth",default:100},critical:{type:"number",title:"Critical Queue Depth",default:500}}}}};function h(e,t){const n=Number(t)>=2,r={...d},i=["serverType","udpPort","secretKey"];return e?(Object.assign(r,m),r.enableNotifications=y,i.push("udpAddress","testAddress","testPort"),n&&(r.reliability=u,r.congestionControl=g,r.bonding=f,r.alertThresholds=b)):n&&(r.reliability=p),{type:"object",required:i,properties:r}}const k="/plugins/signalk-edge-link";let v=0;function x(){return`skel-${Date.now()}-${++v}`}function T(e){return{_id:x(),name:e||"client",serverType:"client",udpPort:4446,secretKey:"",stretchAsciiKey:!1,useMsgpack:!1,usePathDictionary:!1,enableNotifications:!1,protocolVersion:1,udpAddress:"127.0.0.1",helloMessageSender:60,testAddress:"127.0.0.1",testPort:80,pingIntervalTime:1}}function A(e){return{_id:x(),name:e||"server",serverType:"server",udpPort:4446,secretKey:"",stretchAsciiKey:!1,useMsgpack:!1,usePathDictionary:!1,protocolVersion:1}}function w(e){return e._id?e:{...e,_id:x()}}function E(e){const t=h("server"!==e.serverType,e.protocolVersion),{_id:n,...r}=e;return{...(0,a.NV)(o.Ay,t,r),_id:n}}function S(e){if(null===e||"object"!=typeof e)return JSON.stringify(e);if(Array.isArray(e))return"["+e.map(S).join(",")+"]";const t=e;return"{"+Object.keys(t).sort().map(e=>JSON.stringify(e)+":"+S(t[e])).join(",")+"}"}const C={"ui:order":["name","serverType","udpAddress","udpPort","secretKey","stretchAsciiKey","protocolVersion","useMsgpack","usePathDictionary","testAddress","testPort","pingIntervalTime","helloMessageSender","reliability","congestionControl","bonding","enableNotifications","alertThresholds"],secretKey:{"ui:widget":"password","ui:help":"Use 32-character ASCII, 64-character hex, or 44-character base64"},stretchAsciiKey:{"ui:help":"Only applies to 32-char ASCII keys. Must match on both peers."},serverType:{"ui:widget":"select"},reliability:{"ui:classNames":"skel-optional-group"},congestionControl:{"ui:classNames":"skel-optional-group"},bonding:{"ui:classNames":"skel-optional-group"},alertThresholds:{"ui:classNames":"skel-optional-group"}},P={"ui:order":["name","serverType","udpPort","secretKey","stretchAsciiKey","useMsgpack","usePathDictionary","protocolVersion","reliability"],secretKey:{"ui:widget":"password","ui:help":"Use 32-character ASCII, 64-character hex, or 44-character base64"},stretchAsciiKey:{"ui:help":"Only applies to 32-char ASCII keys. Must match on both peers."},serverType:{"ui:widget":"select"}},N=["name","udpPort","secretKey","stretchAsciiKey","useMsgpack","usePathDictionary","protocolVersion"];function I({conn:e,index:t,totalCount:n,expanded:a,onToggle:s,onChange:l,onRemove:c}){const d="server"!==e.serverType,m=h(d,e.protocolVersion),u=d?C:P,p=d?"Client":"Server",g=(e.name||`Connection ${t+1}`).trim(),{_id:f,...y}=e;return r.createElement("div",{className:"skel-card"},r.createElement("div",{className:"skel-card-header",onClick:s,role:"button","aria-expanded":a},r.createElement("span",{className:"skel-badge "+(d?"skel-badge-client":"skel-badge-server")},p),r.createElement("span",{className:"skel-card-title"},g),r.createElement("span",{className:"skel-expand-icon"},a?"▲":"▼"),r.createElement("button",{className:"skel-btn-remove",disabled:n<=1,onClick:e=>{e.stopPropagation(),c()},title:n<=1?"Cannot remove the only connection":"Remove this connection"},"Remove")),a&&r.createElement("div",{className:"skel-card-body"},r.createElement(i.Ay,{schema:m,uiSchema:u,formData:y,validator:o.Ay,onChange:function(t){const n=t.formData;if(n.serverType!==e.serverType){const t={..."server"===n.serverType?A(n.name):T(n.name),_id:e._id};for(const e of N)void 0!==n[e]&&(t[e]=n[e]);return t.serverType=n.serverType,void l(t)}const r={...n,_id:e._id},{_id:i,...o}=r,{_id:a,...s}=e;(function(e,t){const n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(const r of n){if(!Object.prototype.hasOwnProperty.call(t,r))return!1;const n=e[r],i=t[r];if(n!==i){if(null===n||null===i||"object"!=typeof n||"object"!=typeof i)return!1;if(S(n)!==S(i))return!1}}return!0})(o,s)||l(r)},onSubmit:()=>{},liveValidate:!1},r.createElement("div",null))))}const K=function(e){const[t,n]=(0,r.useState)([]),[i,o]=(0,r.useState)(""),[a,l]=(0,r.useState)(!1),[d,m]=(0,r.useState)(!0),[u,p]=(0,r.useState)(null),[g,f]=(0,r.useState)(null),[y,b]=(0,r.useState)(null),[h,v]=(0,r.useState)(0),[x,S]=(0,r.useState)(!1),C=(0,r.useRef)(!1);(0,r.useEffect)(()=>{!async function(){try{const e=await c(`${k}/plugin-config`);if(401===e.status)throw new Error(s);if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);const t=await e.json();if(!t.success)throw new Error(t.error||"Failed to load configuration");const r=t.configuration||{};let i;i=Array.isArray(r.connections)&&r.connections.length>0?r.connections.map(e=>E(w(e))):r.serverType?[E(w(r))]:[T()],n(i),o("string"==typeof r.managementApiToken?r.managementApiToken:""),l(!0===r.requireManagementApiToken),v(0),S(!1)}catch(e){p(e instanceof Error?e.message:String(e))}finally{m(!1)}}()},[]);const P=t.filter(e=>"server"===e.serverType).map(e=>e.udpPort),N=new Set(P.filter((e,t)=>P.indexOf(e)!==t));function K(){S(!0),f(null),b(null)}const D=(0,r.useCallback)(async()=>{if(!C.current){if(0===t.length)return b("At least one connection is required before saving."),void f({type:"error",message:"Cannot save an empty configuration. Add at least one connection."});if(b(null),N.size>0)f({type:"error",message:`Duplicate server ports detected: ${[...N].join(", ")}. Each server must use a unique UDP port.`});else{C.current=!0,f({type:"saving",message:"Saving configuration..."});try{const e=t.map(({_id:e,...t})=>t),n=await c(`${k}/plugin-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({connections:e,managementApiToken:i,requireManagementApiToken:a})});if(401===n.status)throw new Error(s);const r=await n.json();if(!n.ok||!r.success)throw new Error(r.error||"Failed to save");f({type:"success",message:r.message||"Configuration saved. Plugin restarting..."}),S(!1)}catch(e){f({type:"error",message:e instanceof Error?e.message:String(e)})}finally{C.current=!1}}}},[t,N,i,a]);return d?r.createElement("div",{style:{padding:"20px",textAlign:"center"}},"Loading configuration..."):u?r.createElement("div",{style:{padding:"20px"}},r.createElement("div",{className:"skel-alert skel-alert-error"},r.createElement("strong",null,"Error loading configuration:")," ",u)):r.createElement("div",{className:"skel-config"},r.createElement("style",null,'\n.skel-config { font-family: inherit; }\n.skel-dirty-banner {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 14px;\n background: #fff3cd;\n color: #664d03;\n border: 1px solid #ffe69c;\n border-radius: 4px;\n margin-bottom: 12px;\n font-size: 0.88rem;\n}\n.skel-card {\n border: 1px solid #dee2e6;\n border-radius: 6px;\n margin-bottom: 12px;\n overflow: hidden;\n}\n.skel-card-header {\n display: flex;\n align-items: center;\n padding: 10px 14px;\n background: #f8f9fa;\n cursor: pointer;\n user-select: none;\n gap: 10px;\n}\n.skel-card-header:hover { background: #e9ecef; }\n.skel-badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 12px;\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n}\n.skel-badge-server { background: #cfe2ff; color: #084298; }\n.skel-badge-client { background: #d1e7dd; color: #0a3622; }\n.skel-card-title { font-weight: 600; flex: 1; }\n.skel-expand-icon { font-size: 0.8rem; color: #6c757d; }\n.skel-btn-remove {\n background: none;\n border: 1px solid #dc3545;\n color: #dc3545;\n border-radius: 4px;\n padding: 2px 8px;\n font-size: 0.8rem;\n cursor: pointer;\n}\n.skel-btn-remove:hover { background: #dc3545; color: white; }\n.skel-btn-remove:disabled { opacity: 0.4; cursor: default; border-color: #aaa; color: #aaa; }\n.skel-btn-remove:disabled:hover { background: none; }\n.skel-card-body { padding: 16px; border-top: 1px solid #dee2e6; }\n.skel-toolbar {\n display: flex;\n gap: 10px;\n align-items: center;\n margin-top: 16px;\n padding-top: 16px;\n border-top: 1px solid #dee2e6;\n flex-wrap: wrap;\n}\n.skel-btn {\n padding: 7px 16px;\n border-radius: 4px;\n font-size: 0.95rem;\n cursor: pointer;\n border: none;\n}\n.skel-btn-primary { background: #0d6efd; color: white; }\n.skel-btn-primary:hover { background: #0b5ed7; }\n.skel-btn-primary:disabled { background: #6c757d; cursor: default; }\n.skel-btn-secondary { background: white; color: #0d6efd; border: 1px solid #0d6efd; }\n.skel-btn-secondary:hover { background: #e7f0ff; }\n.skel-alert {\n padding: 10px 14px;\n border-radius: 4px;\n margin-bottom: 14px;\n font-size: 0.9rem;\n}\n.skel-alert-success { background: #d1e7dd; color: #0a3622; border: 1px solid #a3cfbb; }\n.skel-alert-error { background: #f8d7da; color: #58151c; border: 1px solid #f1aeb5; }\n.skel-alert-saving { background: #fff3cd; color: #664d03; border: 1px solid #ffe69c; }\n.skel-dup-warn { font-size: 0.8rem; color: #dc3545; margin-top: 4px; }\n.skel-plugin-settings {\n border: 1px solid #dee2e6;\n border-radius: 6px;\n margin-bottom: 20px;\n padding: 16px;\n background: #f8f9fa;\n}\n.skel-plugin-settings h3 {\n margin: 0 0 12px;\n font-size: 1rem;\n font-weight: 600;\n}\n.skel-field-group {\n margin-bottom: 14px;\n}\n.skel-field-group label {\n display: block;\n font-weight: 500;\n margin-bottom: 4px;\n font-size: 0.9rem;\n}\n.skel-field-group input[type="text"],\n.skel-field-group input[type="password"] {\n width: 100%;\n max-width: 420px;\n padding: 6px 10px;\n border: 1px solid #ced4da;\n border-radius: 4px;\n font-size: 0.9rem;\n}\n.skel-field-group input[type="checkbox"] {\n margin-right: 6px;\n}\n.skel-field-desc {\n font-size: 0.8rem;\n color: #5c6773;\n margin-top: 3px;\n}\n.skel-config .field-description {\n color: #5c6773;\n font-size: 0.83rem;\n line-height: 1.35;\n}\n.skel-config legend,\n.skel-config label {\n line-height: 1.2;\n overflow-wrap: anywhere;\n}\n.skel-optional-group {\n margin-top: 12px;\n border: 1px dashed #ccd5df;\n border-radius: 6px;\n padding: 10px 12px 4px;\n background: #fbfcfe;\n}\n.skel-optional-group legend {\n font-size: 0.92rem;\n margin-bottom: 6px;\n}\n.skel-optional-group .form-group {\n margin-bottom: 10px;\n}\n.skel-optional-group .form-control {\n max-width: 340px;\n}\n'),x&&"saving"!==g?.type&&r.createElement("div",{className:"skel-dirty-banner"},r.createElement("span",null,"⚠"),r.createElement("span",null,"You have unsaved changes.")),g&&r.createElement("div",{className:"skel-alert skel-alert-"+("saving"===g.type?"saving":"success"===g.type?"success":"error")},g.message),r.createElement("div",{className:"skel-plugin-settings"},r.createElement("h3",null,"Plugin Security Settings"),r.createElement("div",{className:"skel-field-group"},r.createElement("label",{htmlFor:"skel-mgmt-token"},"Management API Token"),r.createElement("input",{id:"skel-mgmt-token",type:"password",value:i,placeholder:"Leave empty for open access",onChange:e=>{o(e.target.value),K()},autoComplete:"new-password"}),r.createElement("div",{className:"skel-field-desc"},"Shared secret to protect the management API endpoints. Strongly recommended for production. Can also be set via the"," ",r.createElement("code",null,"SIGNALK_EDGE_LINK_MANAGEMENT_TOKEN")," environment variable (env var takes priority). Leave empty to allow open access.")),r.createElement("div",{className:"skel-field-group"},r.createElement("label",null,r.createElement("input",{type:"checkbox",checked:a,onChange:e=>{l(e.target.checked),K()}}),"Require Management API Token"),r.createElement("div",{className:"skel-field-desc"},"When enabled, all management API requests are rejected if no token is configured (fail-closed). When disabled, requests are allowed if no token is set (open access)."))),t.map((e,i)=>r.createElement("div",{key:e._id},r.createElement(I,{conn:e,index:i,totalCount:t.length,expanded:h===i,onToggle:()=>function(e){v(t=>t===e?null:e)}(i),onChange:e=>function(e,t){n(n=>n.map((n,r)=>r===e?t:n)),K()}(i,e),onRemove:()=>function(e){n(t=>{if(t.length<=1)return t;const n=t.filter((t,n)=>n!==e);return v(t=>null!==t&&t>=e&&t>0?t-1:t),n}),K()}(i)}),"server"===e.serverType&&N.has(e.udpPort)&&r.createElement("div",{className:"skel-dup-warn"},"Port ",e.udpPort," is used by multiple server connections. Each server requires a unique port."))),r.createElement("div",{className:"skel-toolbar"},r.createElement("button",{className:"skel-btn skel-btn-secondary",onClick:function(){n(e=>{const t=[...e,A(`server-${e.length+1}`)];return v(t.length-1),t}),K()}},"+ Add Server"),r.createElement("button",{className:"skel-btn skel-btn-secondary",onClick:function(){n(e=>{const t=[...e,T(`client-${e.length+1}`)];return v(t.length-1),t}),K()}},"+ Add Client"),r.createElement("button",{className:"skel-btn skel-btn-primary",onClick:D,disabled:g&&"saving"===g.type||0===t.length},x?"Save Changes":"Save Configuration"),y&&r.createElement("span",{style:{color:"#dc3545",fontSize:"0.85rem",fontWeight:500}},y),r.createElement("span",{style:{fontSize:"0.85rem",color:"#6c757d"}},t.length," connection",1!==t.length?"s":""," · ",t.filter(e=>"server"===e.serverType).length," server",1!==t.filter(e=>"server"===e.serverType).length?"s":"",", ",t.filter(e=>"server"!==e.serverType).length," client",1!==t.filter(e=>"server"!==e.serverType).length?"s":"")))}}}]);
2
- //# sourceMappingURL=982.b207a377ed6542e2fb4a.js.map
1
+ "use strict";(self.webpackChunksignalk_edge_link=self.webpackChunksignalk_edge_link||[]).push([[982],{4353(e,t,n){n.r(t),n.d(t,{default:()=>K});var r=n(4147),i=n(6718),a=n(4810),o=n(7936);const s="Management token required/invalid.",l={token:null,localStorageKey:"signalkEdgeLinkManagementToken",queryParam:"edgeLinkToken",includeTokenInQuery:!1,headerMode:"both"};function c(e,t={}){const n=function(){if("undefined"==typeof window)return l;const e=window.__EDGE_LINK_AUTH__;return e&&"object"==typeof e?{...l,...e}:l}(),r=function(e){if(e.token)return String(e.token).trim();if("undefined"==typeof window)return"";if(e.includeTokenInQuery&&e.queryParam){const t=new URLSearchParams(window.location.search).get(e.queryParam);if(t)return t.trim()}if(e.localStorageKey&&window.localStorage){const t=window.localStorage.getItem(e.localStorageKey);if(t)return t.trim()}return""}(n),i=new Headers(t.headers||{});return function(e,t,n){if(!t)return e;const r=(n||"both").toLowerCase();"x-edge-link-token"!==r&&"token"!==r&&"both"!==r||e.set("X-Edge-Link-Token",t),"authorization"!==r&&"bearer"!==r&&"both"!==r||e.set("Authorization",`Bearer ${t}`)}(i,r,n.headerMode),fetch(e,{...t,headers:i})}const d={name:{type:"string",title:"Connection Name",description:"Human-readable label for this connection (e.g. 'Shore Server', 'Sat Client'). Used to namespace config files and Signal K metrics paths.",default:"connection",maxLength:40},serverType:{type:"string",title:"Operation Mode",description:"Select Server to receive data, or Client to send data.",default:"client",oneOf:[{const:"server",title:"Server Mode – Receive Data"},{const:"client",title:"Client Mode – Send Data"}]},udpPort:{type:"number",title:"UDP Port",description:"UDP port for data transmission (must match on both ends).",default:4446,minimum:1024,maximum:65535},secretKey:{type:"string",title:"Encryption Key",description:"32-byte secret key: 32-character ASCII, 64-character hex, or 44-character base64.",minLength:32,maxLength:64,pattern:"^(?:.{32}|[0-9a-fA-F]{64}|[A-Za-z0-9+/]{43}=?)$"},stretchAsciiKey:{type:"boolean",title:"Stretch 32-char ASCII Key (PBKDF2)",description:`When the secretKey is 32-character ASCII, route it through PBKDF2-SHA256 (${6e5.toLocaleString("en-US")} iterations) to raise it to full 256-bit AES strength. Hex and base64 keys are unaffected. BOTH ENDS OF THE CONNECTION MUST USE THE SAME SETTING — otherwise authentication will fail and every packet will be dropped.`,default:!1},useMsgpack:{type:"boolean",title:"Use MessagePack",description:"Binary serialization for smaller payloads (must match on both ends).",default:!1},usePathDictionary:{type:"boolean",title:"Use Path Dictionary",description:"Encode paths as numeric IDs for bandwidth savings (must match on both ends).",default:!1},protocolVersion:{type:"number",title:"Protocol Version",description:"v1: encrypted UDP. v2 adds reliable delivery and metrics. v3 keeps the v2 data path and authenticates control packets (ACK/NAK/HEARTBEAT/HELLO). Must match on both ends.",default:1,oneOf:[{const:1,title:"v1 – Standard encrypted UDP"},{const:2,title:"v2 – Reliability, congestion control, bonding, metrics"},{const:3,title:"v3 - v2 features with authenticated control packets"}]}},m={udpAddress:{type:"string",title:"Server Address",description:"IP address or hostname of the remote Signal K endpoint.",default:"127.0.0.1"},helloMessageSender:{type:"integer",title:"Heartbeat Interval (seconds)",description:"Send periodic heartbeat messages to keep NAT/firewall mappings alive.",default:60,minimum:10,maximum:3600},testAddress:{type:"string",title:"Connectivity Test Address",description:"Host used for reachability checks (e.g. 8.8.8.8).",default:"127.0.0.1"},testPort:{type:"number",title:"Connectivity Test Port",description:"Port used for reachability checks (e.g. 53, 80, or 443).",default:80,minimum:1,maximum:65535},pingIntervalTime:{type:"number",title:"Check Interval (minutes)",description:"Frequency of network reachability checks.",default:1,minimum:.1,maximum:60},heartbeatInterval:{type:"number",title:"NAT Keepalive Heartbeat Interval (ms)",description:"v2/v3 only. How often to send UDP heartbeat packets for NAT traversal. Typical NAT timeouts range from 30s to 120s.",default:25e3,minimum:5e3,maximum:12e4}},u={type:"object",title:"Reliability Settings (v2/v3 only)",description:"Requires Protocol v2 or v3. Controls retransmit queue behavior and packet retry limits.",properties:{retransmitQueueSize:{type:"number",title:"Retransmit Queue Size",description:"Maximum number of sent packets stored for potential retransmission.",default:5e3,minimum:100,maximum:5e4},maxRetransmits:{type:"number",title:"Max Retransmit Attempts",description:"Maximum resend attempts before a packet is dropped from the retransmit queue.",default:3,minimum:1,maximum:20},retransmitMaxAge:{type:"number",title:"Retransmit Max Age (ms)",description:"Expire stale unacknowledged packets older than this age.",default:12e4,minimum:1e3,maximum:3e5},retransmitMinAge:{type:"number",title:"Retransmit Min Age (ms)",description:"Minimum packet age before expiration is allowed.",default:1e4,minimum:200,maximum:3e4},retransmitRttMultiplier:{type:"number",title:"RTT Expiry Multiplier",description:"Dynamic expiry age = RTT × this multiplier.",default:12,minimum:2,maximum:20},ackIdleDrainAge:{type:"number",title:"ACK Idle Drain Age (ms)",description:"If ACKs are idle longer than this, expiry becomes more aggressive.",default:2e4,minimum:500,maximum:3e4},forceDrainAfterAckIdle:{type:"boolean",title:"Force Drain After ACK Idle",description:"When enabled, clear retransmit queue if no ACKs arrive for too long.",default:!1},forceDrainAfterMs:{type:"number",title:"Force Drain Timeout (ms)",description:"ACK idle duration before force-draining retransmit queue to zero.",default:45e3,minimum:2e3,maximum:12e4},recoveryBurstEnabled:{type:"boolean",title:"Recovery Burst Enabled",description:"When ACKs return after outage, rapidly retransmit queued packets to catch up.",default:!0},recoveryBurstSize:{type:"number",title:"Recovery Burst Size",description:"Max queued packets to retransmit per recovery burst cycle.",default:100,minimum:10,maximum:1e3},recoveryBurstIntervalMs:{type:"number",title:"Recovery Burst Interval (ms)",description:"Interval between recovery burst cycles while backlog exists.",default:200,minimum:50,maximum:5e3},recoveryAckGapMs:{type:"number",title:"Recovery ACK Gap (ms)",description:"Minimum ACK silence before triggering fast recovery bursts.",default:4e3,minimum:500,maximum:12e4}}},p={type:"object",title:"Reliability Settings (v2/v3 only)",description:"Requires Protocol v2 or v3. Controls ACK/NAK timing for reliable delivery.",properties:{ackInterval:{type:"number",title:"ACK Interval (ms)",description:"How often server sends cumulative ACK updates.",default:100,minimum:20,maximum:5e3},ackResendInterval:{type:"number",title:"ACK Resend Interval (ms)",description:"Re-send duplicate ACK periodically to recover from lost ACK packets.",default:1e3,minimum:100,maximum:1e4},nakTimeout:{type:"number",title:"NAK Timeout (ms)",description:"Delay before requesting retransmission for missing sequence numbers.",default:100,minimum:20,maximum:5e3}}},g={type:"object",title:"Dynamic Congestion Control (v2/v3 only)",description:"Requires Protocol v2 or v3. AIMD algorithm to dynamically adjust send rate based on network conditions.",properties:{enabled:{type:"boolean",title:"Enable Congestion Control",description:"Automatically adjust delta timer based on RTT and packet loss.",default:!1},targetRTT:{type:"number",title:"Target RTT (ms)",description:"RTT threshold above which send rate is reduced.",default:200,minimum:50,maximum:2e3},nominalDeltaTimer:{type:"number",title:"Nominal Delta Timer (ms)",description:"Preferred steady-state send interval.",default:1e3,minimum:100,maximum:1e4},minDeltaTimer:{type:"number",title:"Minimum Delta Timer (ms)",description:"Fastest allowed send interval.",default:100,minimum:50,maximum:1e3},maxDeltaTimer:{type:"number",title:"Maximum Delta Timer (ms)",description:"Slowest allowed send interval.",default:5e3,minimum:1e3,maximum:3e4}}},f={type:"object",title:"Connection Bonding (v2/v3 only)",description:"Requires Protocol v2 or v3. Dual-link bonding with automatic failover between primary and backup connections.",properties:{enabled:{type:"boolean",title:"Enable Connection Bonding",description:"Enable dual-link bonding with automatic failover.",default:!1},mode:{type:"string",title:"Bonding Mode",description:"Bonding operating mode.",default:"main-backup",oneOf:[{const:"main-backup",title:"Main/Backup – Failover to backup when primary degrades"}]},primary:{type:"object",title:"Primary Link",description:"Primary connection (e.g. LTE modem).",properties:{address:{type:"string",title:"Server Address",default:"127.0.0.1"},port:{type:"number",title:"UDP Port",default:4446,minimum:1024,maximum:65535},interface:{type:"string",title:"Bind Interface (optional)",description:"Network interface IP to bind to."}}},backup:{type:"object",title:"Backup Link",description:"Backup connection (e.g. Starlink, satellite).",properties:{address:{type:"string",title:"Server Address",default:"127.0.0.1"},port:{type:"number",title:"UDP Port",default:4447,minimum:1024,maximum:65535},interface:{type:"string",title:"Bind Interface (optional)",description:"Network interface IP to bind to."}}},failover:{type:"object",title:"Failover Thresholds",description:"Configure when failover is triggered.",properties:{rttThreshold:{type:"number",title:"RTT Threshold (ms)",default:500,minimum:100,maximum:5e3},lossThreshold:{type:"number",title:"Packet Loss Threshold (0-1)",default:.1,minimum:.01,maximum:.5},healthCheckInterval:{type:"number",title:"Health Check Interval (ms)",default:1e3,minimum:500,maximum:1e4},failbackDelay:{type:"number",title:"Failback Delay (ms)",default:3e4,minimum:5e3,maximum:3e5},heartbeatTimeout:{type:"number",title:"Heartbeat Timeout (ms)",default:5e3,minimum:1e3,maximum:3e4}}}}},y={type:"boolean",title:"Enable Signal K Notifications",description:"Emit Signal K notifications for alerts and failover events.",default:!1},b={type:"object",title:"Monitoring Alert Thresholds (v2/v3 only)",description:"Customize warning/critical thresholds for network monitoring alerts.",properties:{rtt:{type:"object",title:"RTT Thresholds",properties:{warning:{type:"number",title:"Warning RTT (ms)",default:300},critical:{type:"number",title:"Critical RTT (ms)",default:800}}},packetLoss:{type:"object",title:"Packet Loss Thresholds",properties:{warning:{type:"number",title:"Warning Loss Ratio",default:.03},critical:{type:"number",title:"Critical Loss Ratio",default:.1}}},retransmitRate:{type:"object",title:"Retransmit Rate Thresholds",properties:{warning:{type:"number",title:"Warning Retransmit Ratio",default:.05},critical:{type:"number",title:"Critical Retransmit Ratio",default:.15}}},jitter:{type:"object",title:"Jitter Thresholds",properties:{warning:{type:"number",title:"Warning Jitter (ms)",default:100},critical:{type:"number",title:"Critical Jitter (ms)",default:300}}},queueDepth:{type:"object",title:"Queue Depth Thresholds",properties:{warning:{type:"number",title:"Warning Queue Depth",default:100},critical:{type:"number",title:"Critical Queue Depth",default:500}}}}};function h(e,t){const n=Number(t)>=2,r={...d},i=["serverType","udpPort","secretKey"];return e?(Object.assign(r,m),r.enableNotifications=y,i.push("udpAddress","testAddress","testPort"),n&&(r.reliability=u,r.congestionControl=g,r.bonding=f,r.alertThresholds=b)):n&&(r.reliability=p),{type:"object",required:i,properties:r}}const k="/plugins/signalk-edge-link";let v=0;function x(){return`skel-${Date.now()}-${++v}`}function T(e){return{_id:x(),name:e||"client",serverType:"client",udpPort:4446,secretKey:"",stretchAsciiKey:!1,useMsgpack:!1,usePathDictionary:!1,enableNotifications:!1,protocolVersion:1,udpAddress:"127.0.0.1",helloMessageSender:60,testAddress:"127.0.0.1",testPort:80,pingIntervalTime:1}}function A(e){return{_id:x(),name:e||"server",serverType:"server",udpPort:4446,secretKey:"",stretchAsciiKey:!1,useMsgpack:!1,usePathDictionary:!1,protocolVersion:1}}function w(e){return e._id?e:{...e,_id:x()}}function E(e){const t=h("server"!==e.serverType,e.protocolVersion),{_id:n,...r}=e;return{...(0,o.NV)(a.Ay,t,r),_id:n}}function S(e){if(null===e||"object"!=typeof e)return JSON.stringify(e);if(Array.isArray(e))return"["+e.map(S).join(",")+"]";const t=e;return"{"+Object.keys(t).sort().map(e=>JSON.stringify(e)+":"+S(t[e])).join(",")+"}"}const C={"ui:order":["name","serverType","udpAddress","udpPort","secretKey","stretchAsciiKey","protocolVersion","useMsgpack","usePathDictionary","testAddress","testPort","pingIntervalTime","helloMessageSender","heartbeatInterval","reliability","congestionControl","bonding","enableNotifications","alertThresholds"],secretKey:{"ui:widget":"password","ui:help":"Use 32-character ASCII, 64-character hex, or 44-character base64"},stretchAsciiKey:{"ui:help":"Only applies to 32-char ASCII keys. Must match on both peers."},serverType:{"ui:widget":"select"},reliability:{"ui:classNames":"skel-optional-group"},congestionControl:{"ui:classNames":"skel-optional-group"},bonding:{"ui:classNames":"skel-optional-group"},alertThresholds:{"ui:classNames":"skel-optional-group"}},P={"ui:order":["name","serverType","udpPort","secretKey","stretchAsciiKey","useMsgpack","usePathDictionary","protocolVersion","reliability"],secretKey:{"ui:widget":"password","ui:help":"Use 32-character ASCII, 64-character hex, or 44-character base64"},stretchAsciiKey:{"ui:help":"Only applies to 32-char ASCII keys. Must match on both peers."},serverType:{"ui:widget":"select"}},N=["name","udpPort","secretKey","stretchAsciiKey","useMsgpack","usePathDictionary","protocolVersion"];function I({conn:e,index:t,totalCount:n,expanded:o,onToggle:s,onChange:l,onRemove:c}){const d="server"!==e.serverType,m=h(d,e.protocolVersion),u=d?C:P,p=d?"Client":"Server",g=(e.name||`Connection ${t+1}`).trim(),{_id:f,...y}=e;return r.createElement("div",{className:"skel-card"},r.createElement("div",{className:"skel-card-header",onClick:s,role:"button","aria-expanded":o},r.createElement("span",{className:"skel-badge "+(d?"skel-badge-client":"skel-badge-server")},p),r.createElement("span",{className:"skel-card-title"},g),r.createElement("span",{className:"skel-expand-icon"},o?"▲":"▼"),r.createElement("button",{className:"skel-btn-remove",disabled:n<=1,onClick:e=>{e.stopPropagation(),c()},title:n<=1?"Cannot remove the only connection":"Remove this connection"},"Remove")),o&&r.createElement("div",{className:"skel-card-body"},r.createElement(i.Ay,{schema:m,uiSchema:u,formData:y,validator:a.Ay,onChange:function(t){const n=t.formData;if(n.serverType!==e.serverType){const t={..."server"===n.serverType?A(n.name):T(n.name),_id:e._id};for(const e of N)void 0!==n[e]&&(t[e]=n[e]);return t.serverType=n.serverType,void l(t)}const r={...n,_id:e._id},{_id:i,...a}=r,{_id:o,...s}=e;(function(e,t){const n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(const r of n){if(!Object.prototype.hasOwnProperty.call(t,r))return!1;const n=e[r],i=t[r];if(n!==i){if(null===n||null===i||"object"!=typeof n||"object"!=typeof i)return!1;if(S(n)!==S(i))return!1}}return!0})(a,s)||l(r)},onSubmit:()=>{},liveValidate:!1},r.createElement("div",null))))}const K=function(e){const[t,n]=(0,r.useState)([]),[i,a]=(0,r.useState)(""),[o,l]=(0,r.useState)(!1),[d,m]=(0,r.useState)(!0),[u,p]=(0,r.useState)(null),[g,f]=(0,r.useState)(null),[y,b]=(0,r.useState)(null),[h,v]=(0,r.useState)(0),[x,S]=(0,r.useState)(!1),C=(0,r.useRef)(!1);(0,r.useEffect)(()=>{!async function(){try{const e=await c(`${k}/plugin-config`);if(401===e.status)throw new Error(s);if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);const t=await e.json();if(!t.success)throw new Error(t.error||"Failed to load configuration");const r=t.configuration||{};let i;i=Array.isArray(r.connections)&&r.connections.length>0?r.connections.map(e=>E(w(e))):r.serverType?[E(w(r))]:[T()],n(i),a("string"==typeof r.managementApiToken?r.managementApiToken:""),l(!0===r.requireManagementApiToken),v(0),S(!1)}catch(e){p(e instanceof Error?e.message:String(e))}finally{m(!1)}}()},[]);const P=t.filter(e=>"server"===e.serverType).map(e=>e.udpPort),N=new Set(P.filter((e,t)=>P.indexOf(e)!==t));function K(){S(!0),f(null),b(null)}const D=(0,r.useCallback)(async()=>{if(!C.current){if(0===t.length)return b("At least one connection is required before saving."),void f({type:"error",message:"Cannot save an empty configuration. Add at least one connection."});if(b(null),N.size>0)f({type:"error",message:`Duplicate server ports detected: ${[...N].join(", ")}. Each server must use a unique UDP port.`});else{C.current=!0,f({type:"saving",message:"Saving configuration..."});try{const e=t.map(({_id:e,...t})=>t),n=await c(`${k}/plugin-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({connections:e,managementApiToken:i,requireManagementApiToken:o})});if(401===n.status)throw new Error(s);const r=await n.json();if(!n.ok||!r.success)throw new Error(r.error||"Failed to save");f({type:"success",message:r.message||"Configuration saved. Plugin restarting..."}),S(!1)}catch(e){f({type:"error",message:e instanceof Error?e.message:String(e)})}finally{C.current=!1}}}},[t,N,i,o]);return d?r.createElement("div",{style:{padding:"20px",textAlign:"center"}},"Loading configuration..."):u?r.createElement("div",{style:{padding:"20px"}},r.createElement("div",{className:"skel-alert skel-alert-error"},r.createElement("strong",null,"Error loading configuration:")," ",u)):r.createElement("div",{className:"skel-config"},r.createElement("style",null,'\n.skel-config { font-family: inherit; }\n.skel-dirty-banner {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 14px;\n background: #fff3cd;\n color: #664d03;\n border: 1px solid #ffe69c;\n border-radius: 4px;\n margin-bottom: 12px;\n font-size: 0.88rem;\n}\n.skel-card {\n border: 1px solid #dee2e6;\n border-radius: 6px;\n margin-bottom: 12px;\n overflow: hidden;\n}\n.skel-card-header {\n display: flex;\n align-items: center;\n padding: 10px 14px;\n background: #f8f9fa;\n cursor: pointer;\n user-select: none;\n gap: 10px;\n}\n.skel-card-header:hover { background: #e9ecef; }\n.skel-badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 12px;\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n}\n.skel-badge-server { background: #cfe2ff; color: #084298; }\n.skel-badge-client { background: #d1e7dd; color: #0a3622; }\n.skel-card-title { font-weight: 600; flex: 1; }\n.skel-expand-icon { font-size: 0.8rem; color: #6c757d; }\n.skel-btn-remove {\n background: none;\n border: 1px solid #dc3545;\n color: #dc3545;\n border-radius: 4px;\n padding: 2px 8px;\n font-size: 0.8rem;\n cursor: pointer;\n}\n.skel-btn-remove:hover { background: #dc3545; color: white; }\n.skel-btn-remove:disabled { opacity: 0.4; cursor: default; border-color: #aaa; color: #aaa; }\n.skel-btn-remove:disabled:hover { background: none; }\n.skel-card-body { padding: 16px; border-top: 1px solid #dee2e6; }\n.skel-toolbar {\n display: flex;\n gap: 10px;\n align-items: center;\n margin-top: 16px;\n padding-top: 16px;\n border-top: 1px solid #dee2e6;\n flex-wrap: wrap;\n}\n.skel-btn {\n padding: 7px 16px;\n border-radius: 4px;\n font-size: 0.95rem;\n cursor: pointer;\n border: none;\n}\n.skel-btn-primary { background: #0d6efd; color: white; }\n.skel-btn-primary:hover { background: #0b5ed7; }\n.skel-btn-primary:disabled { background: #6c757d; cursor: default; }\n.skel-btn-secondary { background: white; color: #0d6efd; border: 1px solid #0d6efd; }\n.skel-btn-secondary:hover { background: #e7f0ff; }\n.skel-alert {\n padding: 10px 14px;\n border-radius: 4px;\n margin-bottom: 14px;\n font-size: 0.9rem;\n}\n.skel-alert-success { background: #d1e7dd; color: #0a3622; border: 1px solid #a3cfbb; }\n.skel-alert-error { background: #f8d7da; color: #58151c; border: 1px solid #f1aeb5; }\n.skel-alert-saving { background: #fff3cd; color: #664d03; border: 1px solid #ffe69c; }\n.skel-dup-warn { font-size: 0.8rem; color: #dc3545; margin-top: 4px; }\n.skel-plugin-settings {\n border: 1px solid #dee2e6;\n border-radius: 6px;\n margin-bottom: 20px;\n padding: 16px;\n background: #f8f9fa;\n}\n.skel-plugin-settings h3 {\n margin: 0 0 12px;\n font-size: 1rem;\n font-weight: 600;\n}\n.skel-field-group {\n margin-bottom: 14px;\n}\n.skel-field-group label {\n display: block;\n font-weight: 500;\n margin-bottom: 4px;\n font-size: 0.9rem;\n}\n.skel-field-group input[type="text"],\n.skel-field-group input[type="password"] {\n width: 100%;\n max-width: 420px;\n padding: 6px 10px;\n border: 1px solid #ced4da;\n border-radius: 4px;\n font-size: 0.9rem;\n}\n.skel-field-group input[type="checkbox"] {\n margin-right: 6px;\n}\n.skel-field-desc {\n font-size: 0.8rem;\n color: #5c6773;\n margin-top: 3px;\n}\n.skel-config .field-description {\n color: #5c6773;\n font-size: 0.83rem;\n line-height: 1.35;\n}\n.skel-config legend,\n.skel-config label {\n line-height: 1.2;\n overflow-wrap: anywhere;\n}\n.skel-optional-group {\n margin-top: 12px;\n border: 1px dashed #ccd5df;\n border-radius: 6px;\n padding: 10px 12px 4px;\n background: #fbfcfe;\n}\n.skel-optional-group legend {\n font-size: 0.92rem;\n margin-bottom: 6px;\n}\n.skel-optional-group .form-group {\n margin-bottom: 10px;\n}\n.skel-optional-group .form-control {\n max-width: 340px;\n}\n'),x&&"saving"!==g?.type&&r.createElement("div",{className:"skel-dirty-banner"},r.createElement("span",null,"⚠"),r.createElement("span",null,"You have unsaved changes.")),g&&r.createElement("div",{className:"skel-alert skel-alert-"+("saving"===g.type?"saving":"success"===g.type?"success":"error")},g.message),r.createElement("div",{className:"skel-plugin-settings"},r.createElement("h3",null,"Plugin Security Settings"),r.createElement("div",{className:"skel-field-group"},r.createElement("label",{htmlFor:"skel-mgmt-token"},"Management API Token"),r.createElement("input",{id:"skel-mgmt-token",type:"password",value:i,placeholder:"Leave empty for open access",onChange:e=>{a(e.target.value),K()},autoComplete:"new-password"}),r.createElement("div",{className:"skel-field-desc"},"Shared secret to protect the management API endpoints. Strongly recommended for production. Can also be set via the"," ",r.createElement("code",null,"SIGNALK_EDGE_LINK_MANAGEMENT_TOKEN")," environment variable (env var takes priority). Leave empty to allow open access.")),r.createElement("div",{className:"skel-field-group"},r.createElement("label",null,r.createElement("input",{type:"checkbox",checked:o,onChange:e=>{l(e.target.checked),K()}}),"Require Management API Token"),r.createElement("div",{className:"skel-field-desc"},"When enabled, all management API requests are rejected if no token is configured (fail-closed). When disabled, requests are allowed if no token is set (open access)."))),t.map((e,i)=>r.createElement("div",{key:e._id},r.createElement(I,{conn:e,index:i,totalCount:t.length,expanded:h===i,onToggle:()=>function(e){v(t=>t===e?null:e)}(i),onChange:e=>function(e,t){n(n=>n.map((n,r)=>r===e?t:n)),K()}(i,e),onRemove:()=>function(e){n(t=>{if(t.length<=1)return t;const n=t.filter((t,n)=>n!==e);return v(t=>null!==t&&t>=e&&t>0?t-1:t),n}),K()}(i)}),"server"===e.serverType&&N.has(e.udpPort)&&r.createElement("div",{className:"skel-dup-warn"},"Port ",e.udpPort," is used by multiple server connections. Each server requires a unique port."))),r.createElement("div",{className:"skel-toolbar"},r.createElement("button",{className:"skel-btn skel-btn-secondary",onClick:function(){n(e=>{const t=[...e,A(`server-${e.length+1}`)];return v(t.length-1),t}),K()}},"+ Add Server"),r.createElement("button",{className:"skel-btn skel-btn-secondary",onClick:function(){n(e=>{const t=[...e,T(`client-${e.length+1}`)];return v(t.length-1),t}),K()}},"+ Add Client"),r.createElement("button",{className:"skel-btn skel-btn-primary",onClick:D,disabled:g&&"saving"===g.type||0===t.length},x?"Save Changes":"Save Configuration"),y&&r.createElement("span",{style:{color:"#dc3545",fontSize:"0.85rem",fontWeight:500}},y),r.createElement("span",{style:{fontSize:"0.85rem",color:"#6c757d"}},t.length," connection",1!==t.length?"s":""," · ",t.filter(e=>"server"===e.serverType).length," server",1!==t.filter(e=>"server"===e.serverType).length?"s":"",", ",t.filter(e=>"server"!==e.serverType).length," client",1!==t.filter(e=>"server"!==e.serverType).length?"s":"")))}}}]);
2
+ //# sourceMappingURL=982.63949a2b2f6c5854e034.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"982.63949a2b2f6c5854e034.js","mappings":"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","sources":["webpack://signalk-edge-link/./src/webapp/utils/apiFetch.ts","webpack://signalk-edge-link/./src/shared/crypto-constants.ts","webpack://signalk-edge-link/./src/shared/connection-schema.ts","webpack://signalk-edge-link/./src/webapp/components/PluginConfigurationPanel.tsx"],"sourcesContent":["/// <reference lib=\"dom\" />\r\n\r\nexport const MANAGEMENT_TOKEN_ERROR_MESSAGE = \"Management token required/invalid.\";\r\n\r\ninterface AuthConfig {\r\n token: string | null;\r\n localStorageKey: string;\r\n queryParam: string;\r\n includeTokenInQuery: boolean;\r\n headerMode: string;\r\n}\r\n\r\ndeclare global {\r\n interface Window {\r\n __EDGE_LINK_AUTH__?: Partial<AuthConfig>;\r\n }\r\n}\r\n\r\nconst DEFAULT_AUTH_CONFIG: AuthConfig = {\r\n token: null,\r\n localStorageKey: \"signalkEdgeLinkManagementToken\",\r\n queryParam: \"edgeLinkToken\",\r\n // Default to false: query-parameter tokens leak into browser history, server\r\n // access logs, and Referer headers. Set includeTokenInQuery: true in\r\n // window.__EDGE_LINK_AUTH__ only when you explicitly need URL-based auth.\r\n includeTokenInQuery: false,\r\n headerMode: \"both\"\r\n};\r\n\r\nfunction readRuntimeAuthConfig(): AuthConfig {\r\n if (typeof window === \"undefined\") {\r\n return DEFAULT_AUTH_CONFIG;\r\n }\r\n\r\n const runtime = window.__EDGE_LINK_AUTH__;\r\n if (!runtime || typeof runtime !== \"object\") {\r\n return DEFAULT_AUTH_CONFIG;\r\n }\r\n\r\n return { ...DEFAULT_AUTH_CONFIG, ...runtime };\r\n}\r\n\r\nfunction resolveToken(config: AuthConfig): string {\r\n if (config.token) {\r\n return String(config.token).trim();\r\n }\r\n\r\n if (typeof window === \"undefined\") {\r\n return \"\";\r\n }\r\n\r\n // SECURITY NOTE: Query parameter tokens can leak into browser history, server\r\n // access logs, and Referer headers. Prefer localStorage or\r\n // window.__EDGE_LINK_AUTH__.token for production deployments. Set\r\n // includeTokenInQuery: false in __EDGE_LINK_AUTH__ to disable this path.\r\n if (config.includeTokenInQuery && config.queryParam) {\r\n const tokenFromQuery = new URLSearchParams(window.location.search).get(config.queryParam);\r\n if (tokenFromQuery) {\r\n return tokenFromQuery.trim();\r\n }\r\n }\r\n\r\n if (config.localStorageKey && window.localStorage) {\r\n const tokenFromStorage = window.localStorage.getItem(config.localStorageKey);\r\n if (tokenFromStorage) {\r\n return tokenFromStorage.trim();\r\n }\r\n }\r\n\r\n return \"\";\r\n}\r\n\r\nfunction attachAuthHeaders(headers: Headers, token: string, headerMode: string): Headers {\r\n if (!token) {\r\n return headers;\r\n }\r\n\r\n const normalizedMode = (headerMode || \"both\").toLowerCase();\r\n if (\r\n normalizedMode === \"x-edge-link-token\" ||\r\n normalizedMode === \"token\" ||\r\n normalizedMode === \"both\"\r\n ) {\r\n headers.set(\"X-Edge-Link-Token\", token);\r\n }\r\n if (\r\n normalizedMode === \"authorization\" ||\r\n normalizedMode === \"bearer\" ||\r\n normalizedMode === \"both\"\r\n ) {\r\n headers.set(\"Authorization\", `Bearer ${token}`);\r\n }\r\n return headers;\r\n}\r\n\r\nexport function getAuthToken(): string {\r\n const config = readRuntimeAuthConfig();\r\n return resolveToken(config);\r\n}\r\n\r\nexport function getTokenHelpText(): string {\r\n const config = readRuntimeAuthConfig();\r\n const modeText =\r\n config.headerMode && String(config.headerMode).toLowerCase() === \"authorization\"\r\n ? \"Authorization: Bearer <token>\"\r\n : config.headerMode && String(config.headerMode).toLowerCase() === \"x-edge-link-token\"\r\n ? \"X-Edge-Link-Token\"\r\n : \"X-Edge-Link-Token and Authorization: Bearer <token>\";\r\n\r\n return `The server-side token is configured in plugin settings (managementApiToken) or via the SIGNALK_EDGE_LINK_MANAGEMENT_TOKEN environment variable. To authenticate from the browser, provide the token using window.__EDGE_LINK_AUTH__.token, query parameter \"${config.queryParam}\", or localStorage key \"${config.localStorageKey}\". Requests send ${modeText} when a token is available.`;\r\n}\r\n\r\nexport function apiFetch(input: string | Request, init: RequestInit = {}): Promise<Response> {\r\n const config = readRuntimeAuthConfig();\r\n const token = resolveToken(config);\r\n const headers = new Headers(init.headers || {});\r\n attachAuthHeaders(headers, token, config.headerMode);\r\n\r\n return fetch(input, {\r\n ...init,\r\n headers\r\n });\r\n}\r\n","\"use strict\";\r\n\r\n/**\r\n * Shared crypto constants that must stay in sync between the backend crypto\r\n * module and any UI copy that describes key-derivation behaviour. Kept under\r\n * `src/shared/` so both the server-side build and the webapp bundle can\r\n * reference the same value.\r\n */\r\n\r\n/**\r\n * PBKDF2-SHA256 iteration count used by {@link deriveKeyFromPassphrase} and\r\n * by the opt-in 32-char ASCII key stretching path in {@link normalizeKey}.\r\n *\r\n * Tuned to the NIST SP 800-132 recommendation (≥ 600,000) and takes roughly\r\n * ~300 ms on modern server hardware. The derived key is cached per-process\r\n * so the cost is paid at most once per unique (key, salt) pair.\r\n */\r\nexport const PBKDF2_ITERATIONS = 600_000;\r\n","/**\r\n * Single source of truth for the connection configuration schema.\r\n *\r\n * Both the backend `plugin.schema` in `src/index.ts` (used by Signal K's\r\n * default admin UI and served via the `/plugin-schema` route for default\r\n * extraction) and the frontend RJSF form in\r\n * `src/webapp/components/PluginConfigurationPanel.tsx` consume the fragments\r\n * exported here. Adding or editing a connection field must happen in this\r\n * module; the two consumers then render it identically.\r\n *\r\n * The fragments are typed as plain `Record<string, unknown>` so they can be\r\n * imported by both the server-side TypeScript build and the webapp build\r\n * without pulling `@rjsf/utils` into the server bundle. The webapp casts\r\n * results to `RJSFSchema` at call sites.\r\n */\r\n\r\nimport { PBKDF2_ITERATIONS } from \"./crypto-constants\";\r\n\r\nexport type SchemaFragment = Record<string, unknown>;\r\n\r\n// ── Common (client + server) ──────────────────────────────────────────────────\r\n\r\nexport const commonConnectionProperties: Record<string, SchemaFragment> = {\r\n name: {\r\n type: \"string\",\r\n title: \"Connection Name\",\r\n description:\r\n \"Human-readable label for this connection (e.g. 'Shore Server', 'Sat Client'). Used to namespace config files and Signal K metrics paths.\",\r\n default: \"connection\",\r\n maxLength: 40\r\n },\r\n serverType: {\r\n type: \"string\",\r\n title: \"Operation Mode\",\r\n description: \"Select Server to receive data, or Client to send data.\",\r\n default: \"client\",\r\n oneOf: [\r\n { const: \"server\", title: \"Server Mode – Receive Data\" },\r\n { const: \"client\", title: \"Client Mode – Send Data\" }\r\n ]\r\n },\r\n udpPort: {\r\n type: \"number\",\r\n title: \"UDP Port\",\r\n description: \"UDP port for data transmission (must match on both ends).\",\r\n default: 4446,\r\n minimum: 1024,\r\n maximum: 65535\r\n },\r\n secretKey: {\r\n type: \"string\",\r\n title: \"Encryption Key\",\r\n description:\r\n \"32-byte secret key: 32-character ASCII, 64-character hex, or 44-character base64.\",\r\n minLength: 32,\r\n maxLength: 64,\r\n pattern: \"^(?:.{32}|[0-9a-fA-F]{64}|[A-Za-z0-9+/]{43}=?)$\"\r\n },\r\n stretchAsciiKey: {\r\n type: \"boolean\",\r\n title: \"Stretch 32-char ASCII Key (PBKDF2)\",\r\n description: `When the secretKey is 32-character ASCII, route it through PBKDF2-SHA256 (${PBKDF2_ITERATIONS.toLocaleString(\"en-US\")} iterations) to raise it to full 256-bit AES strength. Hex and base64 keys are unaffected. BOTH ENDS OF THE CONNECTION MUST USE THE SAME SETTING — otherwise authentication will fail and every packet will be dropped.`,\r\n default: false\r\n },\r\n useMsgpack: {\r\n type: \"boolean\",\r\n title: \"Use MessagePack\",\r\n description: \"Binary serialization for smaller payloads (must match on both ends).\",\r\n default: false\r\n },\r\n usePathDictionary: {\r\n type: \"boolean\",\r\n title: \"Use Path Dictionary\",\r\n description: \"Encode paths as numeric IDs for bandwidth savings (must match on both ends).\",\r\n default: false\r\n },\r\n protocolVersion: {\r\n type: \"number\",\r\n title: \"Protocol Version\",\r\n description:\r\n \"v1: encrypted UDP. v2 adds reliable delivery and metrics. v3 keeps the v2 data path and authenticates control packets (ACK/NAK/HEARTBEAT/HELLO). Must match on both ends.\",\r\n default: 1,\r\n oneOf: [\r\n { const: 1, title: \"v1 – Standard encrypted UDP\" },\r\n { const: 2, title: \"v2 – Reliability, congestion control, bonding, metrics\" },\r\n { const: 3, title: \"v3 - v2 features with authenticated control packets\" }\r\n ]\r\n }\r\n};\r\n\r\n// ── Client-only transport / reachability fields ───────────────────────────────\r\n\r\nexport const clientTransportProperties: Record<string, SchemaFragment> = {\r\n udpAddress: {\r\n type: \"string\",\r\n title: \"Server Address\",\r\n description: \"IP address or hostname of the remote Signal K endpoint.\",\r\n default: \"127.0.0.1\"\r\n },\r\n helloMessageSender: {\r\n type: \"integer\",\r\n title: \"Heartbeat Interval (seconds)\",\r\n description: \"Send periodic heartbeat messages to keep NAT/firewall mappings alive.\",\r\n default: 60,\r\n minimum: 10,\r\n maximum: 3600\r\n },\r\n testAddress: {\r\n type: \"string\",\r\n title: \"Connectivity Test Address\",\r\n description: \"Host used for reachability checks (e.g. 8.8.8.8).\",\r\n default: \"127.0.0.1\"\r\n },\r\n testPort: {\r\n type: \"number\",\r\n title: \"Connectivity Test Port\",\r\n description: \"Port used for reachability checks (e.g. 53, 80, or 443).\",\r\n default: 80,\r\n minimum: 1,\r\n maximum: 65535\r\n },\r\n pingIntervalTime: {\r\n type: \"number\",\r\n title: \"Check Interval (minutes)\",\r\n description: \"Frequency of network reachability checks.\",\r\n default: 1,\r\n minimum: 0.1,\r\n maximum: 60\r\n },\r\n heartbeatInterval: {\r\n type: \"number\",\r\n title: \"NAT Keepalive Heartbeat Interval (ms)\",\r\n description:\r\n \"v2/v3 only. How often to send UDP heartbeat packets for NAT traversal. Typical NAT timeouts range from 30s to 120s.\",\r\n default: 25000,\r\n minimum: 5000,\r\n maximum: 120000\r\n }\r\n};\r\n\r\n// ── v2/v3 reliability (client pipeline — retransmit queue) ────────────────────\r\n\r\nexport const clientReliabilityProperty: SchemaFragment = {\r\n type: \"object\",\r\n title: \"Reliability Settings (v2/v3 only)\",\r\n description:\r\n \"Requires Protocol v2 or v3. Controls retransmit queue behavior and packet retry limits.\",\r\n properties: {\r\n retransmitQueueSize: {\r\n type: \"number\",\r\n title: \"Retransmit Queue Size\",\r\n description: \"Maximum number of sent packets stored for potential retransmission.\",\r\n default: 5000,\r\n minimum: 100,\r\n maximum: 50000\r\n },\r\n maxRetransmits: {\r\n type: \"number\",\r\n title: \"Max Retransmit Attempts\",\r\n description: \"Maximum resend attempts before a packet is dropped from the retransmit queue.\",\r\n default: 3,\r\n minimum: 1,\r\n maximum: 20\r\n },\r\n retransmitMaxAge: {\r\n type: \"number\",\r\n title: \"Retransmit Max Age (ms)\",\r\n description: \"Expire stale unacknowledged packets older than this age.\",\r\n default: 120000,\r\n minimum: 1000,\r\n maximum: 300000\r\n },\r\n retransmitMinAge: {\r\n type: \"number\",\r\n title: \"Retransmit Min Age (ms)\",\r\n description: \"Minimum packet age before expiration is allowed.\",\r\n default: 10000,\r\n minimum: 200,\r\n maximum: 30000\r\n },\r\n retransmitRttMultiplier: {\r\n type: \"number\",\r\n title: \"RTT Expiry Multiplier\",\r\n description: \"Dynamic expiry age = RTT × this multiplier.\",\r\n default: 12,\r\n minimum: 2,\r\n maximum: 20\r\n },\r\n ackIdleDrainAge: {\r\n type: \"number\",\r\n title: \"ACK Idle Drain Age (ms)\",\r\n description: \"If ACKs are idle longer than this, expiry becomes more aggressive.\",\r\n default: 20000,\r\n minimum: 500,\r\n maximum: 30000\r\n },\r\n forceDrainAfterAckIdle: {\r\n type: \"boolean\",\r\n title: \"Force Drain After ACK Idle\",\r\n description: \"When enabled, clear retransmit queue if no ACKs arrive for too long.\",\r\n default: false\r\n },\r\n forceDrainAfterMs: {\r\n type: \"number\",\r\n title: \"Force Drain Timeout (ms)\",\r\n description: \"ACK idle duration before force-draining retransmit queue to zero.\",\r\n default: 45000,\r\n minimum: 2000,\r\n maximum: 120000\r\n },\r\n recoveryBurstEnabled: {\r\n type: \"boolean\",\r\n title: \"Recovery Burst Enabled\",\r\n description: \"When ACKs return after outage, rapidly retransmit queued packets to catch up.\",\r\n default: true\r\n },\r\n recoveryBurstSize: {\r\n type: \"number\",\r\n title: \"Recovery Burst Size\",\r\n description: \"Max queued packets to retransmit per recovery burst cycle.\",\r\n default: 100,\r\n minimum: 10,\r\n maximum: 1000\r\n },\r\n recoveryBurstIntervalMs: {\r\n type: \"number\",\r\n title: \"Recovery Burst Interval (ms)\",\r\n description: \"Interval between recovery burst cycles while backlog exists.\",\r\n default: 200,\r\n minimum: 50,\r\n maximum: 5000\r\n },\r\n recoveryAckGapMs: {\r\n type: \"number\",\r\n title: \"Recovery ACK Gap (ms)\",\r\n description: \"Minimum ACK silence before triggering fast recovery bursts.\",\r\n default: 4000,\r\n minimum: 500,\r\n maximum: 120000\r\n }\r\n }\r\n};\r\n\r\n// ── v2/v3 reliability (server pipeline — ACK/NAK timing) ──────────────────────\r\n\r\nexport const serverReliabilityProperty: SchemaFragment = {\r\n type: \"object\",\r\n title: \"Reliability Settings (v2/v3 only)\",\r\n description: \"Requires Protocol v2 or v3. Controls ACK/NAK timing for reliable delivery.\",\r\n properties: {\r\n ackInterval: {\r\n type: \"number\",\r\n title: \"ACK Interval (ms)\",\r\n description: \"How often server sends cumulative ACK updates.\",\r\n default: 100,\r\n minimum: 20,\r\n maximum: 5000\r\n },\r\n ackResendInterval: {\r\n type: \"number\",\r\n title: \"ACK Resend Interval (ms)\",\r\n description: \"Re-send duplicate ACK periodically to recover from lost ACK packets.\",\r\n default: 1000,\r\n minimum: 100,\r\n maximum: 10000\r\n },\r\n nakTimeout: {\r\n type: \"number\",\r\n title: \"NAK Timeout (ms)\",\r\n description: \"Delay before requesting retransmission for missing sequence numbers.\",\r\n default: 100,\r\n minimum: 20,\r\n maximum: 5000\r\n }\r\n }\r\n};\r\n\r\n// ── v2/v3 congestion control (client) ─────────────────────────────────────────\r\n\r\nexport const congestionControlProperty: SchemaFragment = {\r\n type: \"object\",\r\n title: \"Dynamic Congestion Control (v2/v3 only)\",\r\n description:\r\n \"Requires Protocol v2 or v3. AIMD algorithm to dynamically adjust send rate based on network conditions.\",\r\n properties: {\r\n enabled: {\r\n type: \"boolean\",\r\n title: \"Enable Congestion Control\",\r\n description: \"Automatically adjust delta timer based on RTT and packet loss.\",\r\n default: false\r\n },\r\n targetRTT: {\r\n type: \"number\",\r\n title: \"Target RTT (ms)\",\r\n description: \"RTT threshold above which send rate is reduced.\",\r\n default: 200,\r\n minimum: 50,\r\n maximum: 2000\r\n },\r\n nominalDeltaTimer: {\r\n type: \"number\",\r\n title: \"Nominal Delta Timer (ms)\",\r\n description: \"Preferred steady-state send interval.\",\r\n default: 1000,\r\n minimum: 100,\r\n maximum: 10000\r\n },\r\n minDeltaTimer: {\r\n type: \"number\",\r\n title: \"Minimum Delta Timer (ms)\",\r\n description: \"Fastest allowed send interval.\",\r\n default: 100,\r\n minimum: 50,\r\n maximum: 1000\r\n },\r\n maxDeltaTimer: {\r\n type: \"number\",\r\n title: \"Maximum Delta Timer (ms)\",\r\n description: \"Slowest allowed send interval.\",\r\n default: 5000,\r\n minimum: 1000,\r\n maximum: 30000\r\n }\r\n }\r\n};\r\n\r\n// ── v2/v3 connection bonding (client) ─────────────────────────────────────────\r\n\r\nexport const bondingProperty: SchemaFragment = {\r\n type: \"object\",\r\n title: \"Connection Bonding (v2/v3 only)\",\r\n description:\r\n \"Requires Protocol v2 or v3. Dual-link bonding with automatic failover between primary and backup connections.\",\r\n properties: {\r\n enabled: {\r\n type: \"boolean\",\r\n title: \"Enable Connection Bonding\",\r\n description: \"Enable dual-link bonding with automatic failover.\",\r\n default: false\r\n },\r\n mode: {\r\n type: \"string\",\r\n title: \"Bonding Mode\",\r\n description: \"Bonding operating mode.\",\r\n default: \"main-backup\",\r\n oneOf: [\r\n {\r\n const: \"main-backup\",\r\n title: \"Main/Backup – Failover to backup when primary degrades\"\r\n }\r\n ]\r\n },\r\n primary: {\r\n type: \"object\",\r\n title: \"Primary Link\",\r\n description: \"Primary connection (e.g. LTE modem).\",\r\n properties: {\r\n address: { type: \"string\", title: \"Server Address\", default: \"127.0.0.1\" },\r\n port: {\r\n type: \"number\",\r\n title: \"UDP Port\",\r\n default: 4446,\r\n minimum: 1024,\r\n maximum: 65535\r\n },\r\n interface: {\r\n type: \"string\",\r\n title: \"Bind Interface (optional)\",\r\n description: \"Network interface IP to bind to.\"\r\n }\r\n }\r\n },\r\n backup: {\r\n type: \"object\",\r\n title: \"Backup Link\",\r\n description: \"Backup connection (e.g. Starlink, satellite).\",\r\n properties: {\r\n address: { type: \"string\", title: \"Server Address\", default: \"127.0.0.1\" },\r\n port: {\r\n type: \"number\",\r\n title: \"UDP Port\",\r\n default: 4447,\r\n minimum: 1024,\r\n maximum: 65535\r\n },\r\n interface: {\r\n type: \"string\",\r\n title: \"Bind Interface (optional)\",\r\n description: \"Network interface IP to bind to.\"\r\n }\r\n }\r\n },\r\n failover: {\r\n type: \"object\",\r\n title: \"Failover Thresholds\",\r\n description: \"Configure when failover is triggered.\",\r\n properties: {\r\n rttThreshold: {\r\n type: \"number\",\r\n title: \"RTT Threshold (ms)\",\r\n default: 500,\r\n minimum: 100,\r\n maximum: 5000\r\n },\r\n lossThreshold: {\r\n type: \"number\",\r\n title: \"Packet Loss Threshold (0-1)\",\r\n default: 0.1,\r\n minimum: 0.01,\r\n maximum: 0.5\r\n },\r\n healthCheckInterval: {\r\n type: \"number\",\r\n title: \"Health Check Interval (ms)\",\r\n default: 1000,\r\n minimum: 500,\r\n maximum: 10000\r\n },\r\n failbackDelay: {\r\n type: \"number\",\r\n title: \"Failback Delay (ms)\",\r\n default: 30000,\r\n minimum: 5000,\r\n maximum: 300000\r\n },\r\n heartbeatTimeout: {\r\n type: \"number\",\r\n title: \"Heartbeat Timeout (ms)\",\r\n default: 5000,\r\n minimum: 1000,\r\n maximum: 30000\r\n }\r\n }\r\n }\r\n }\r\n};\r\n\r\n// ── Client-only notifications toggle ──────────────────────────────────────────\r\n\r\nexport const enableNotificationsProperty: SchemaFragment = {\r\n type: \"boolean\",\r\n title: \"Enable Signal K Notifications\",\r\n description: \"Emit Signal K notifications for alerts and failover events.\",\r\n default: false\r\n};\r\n\r\n// ── v2/v3 monitoring alert thresholds (client) ────────────────────────────────\r\n\r\nexport const alertThresholdsProperty: SchemaFragment = {\r\n type: \"object\",\r\n title: \"Monitoring Alert Thresholds (v2/v3 only)\",\r\n description: \"Customize warning/critical thresholds for network monitoring alerts.\",\r\n properties: {\r\n rtt: {\r\n type: \"object\",\r\n title: \"RTT Thresholds\",\r\n properties: {\r\n warning: { type: \"number\", title: \"Warning RTT (ms)\", default: 300 },\r\n critical: { type: \"number\", title: \"Critical RTT (ms)\", default: 800 }\r\n }\r\n },\r\n packetLoss: {\r\n type: \"object\",\r\n title: \"Packet Loss Thresholds\",\r\n properties: {\r\n warning: { type: \"number\", title: \"Warning Loss Ratio\", default: 0.03 },\r\n critical: { type: \"number\", title: \"Critical Loss Ratio\", default: 0.1 }\r\n }\r\n },\r\n retransmitRate: {\r\n type: \"object\",\r\n title: \"Retransmit Rate Thresholds\",\r\n properties: {\r\n warning: { type: \"number\", title: \"Warning Retransmit Ratio\", default: 0.05 },\r\n critical: { type: \"number\", title: \"Critical Retransmit Ratio\", default: 0.15 }\r\n }\r\n },\r\n jitter: {\r\n type: \"object\",\r\n title: \"Jitter Thresholds\",\r\n properties: {\r\n warning: { type: \"number\", title: \"Warning Jitter (ms)\", default: 100 },\r\n critical: { type: \"number\", title: \"Critical Jitter (ms)\", default: 300 }\r\n }\r\n },\r\n queueDepth: {\r\n type: \"object\",\r\n title: \"Queue Depth Thresholds\",\r\n properties: {\r\n warning: { type: \"number\", title: \"Warning Queue Depth\", default: 100 },\r\n critical: { type: \"number\", title: \"Critical Queue Depth\", default: 500 }\r\n }\r\n }\r\n }\r\n};\r\n\r\n// ── Builder consumed by the backend (`plugin.schema` in src/index.ts) ─────────\r\n\r\n/**\r\n * Build the `connections[]` item schema used by Signal K's default admin UI\r\n * and served via `GET /plugin-schema`. Client-only fields live under\r\n * `dependencies.serverType.oneOf` so they appear only in client mode.\r\n */\r\nexport function buildConnectionItemSchema(): SchemaFragment {\r\n return {\r\n type: \"object\",\r\n title: \"Connection\",\r\n required: [\"serverType\", \"udpPort\", \"secretKey\"],\r\n properties: { ...commonConnectionProperties },\r\n dependencies: {\r\n serverType: {\r\n oneOf: [\r\n {\r\n properties: {\r\n serverType: { enum: [\"server\"] },\r\n reliability: serverReliabilityProperty\r\n }\r\n },\r\n {\r\n properties: {\r\n serverType: { enum: [\"client\"] },\r\n ...clientTransportProperties,\r\n reliability: clientReliabilityProperty,\r\n congestionControl: congestionControlProperty,\r\n bonding: bondingProperty,\r\n enableNotifications: enableNotificationsProperty,\r\n alertThresholds: alertThresholdsProperty\r\n },\r\n required: [\"udpAddress\", \"testAddress\", \"testPort\"]\r\n }\r\n ]\r\n }\r\n }\r\n };\r\n}\r\n\r\n// ── Builder consumed by the webapp (PluginConfigurationPanel.tsx) ─────────────\r\n\r\n/**\r\n * Build the flat per-connection schema consumed by the webapp RJSF form.\r\n * Unlike the backend variant this is a flat object that is rebuilt whenever\r\n * the user toggles `serverType` or `protocolVersion` so RJSF re-renders with\r\n * the right subset of fields.\r\n */\r\nexport function buildWebappConnectionSchema(\r\n isClient: boolean,\r\n protocolVersion: number | undefined\r\n): SchemaFragment {\r\n const isReliableProtocol = Number(protocolVersion) >= 2;\r\n const props: Record<string, SchemaFragment> = { ...commonConnectionProperties };\r\n const required = [\"serverType\", \"udpPort\", \"secretKey\"];\r\n\r\n if (isClient) {\r\n Object.assign(props, clientTransportProperties);\r\n props.enableNotifications = enableNotificationsProperty;\r\n required.push(\"udpAddress\", \"testAddress\", \"testPort\");\r\n if (isReliableProtocol) {\r\n props.reliability = clientReliabilityProperty;\r\n props.congestionControl = congestionControlProperty;\r\n props.bonding = bondingProperty;\r\n props.alertThresholds = alertThresholdsProperty;\r\n }\r\n } else if (isReliableProtocol) {\r\n props.reliability = serverReliabilityProperty;\r\n }\r\n\r\n return { type: \"object\", required, properties: props };\r\n}\r\n","import React from 'react';\r\nimport { useState, useEffect, useCallback, useRef } from 'react';\r\nimport Form from \"@rjsf/core\";\r\nimport validator from \"@rjsf/validator-ajv8\";\r\nimport { RJSFSchema, UiSchema, getDefaultFormState } from \"@rjsf/utils\";\r\nimport { apiFetch, MANAGEMENT_TOKEN_ERROR_MESSAGE } from \"../utils/apiFetch\";\r\nimport { buildWebappConnectionSchema } from \"../../shared/connection-schema\";\r\n\r\nconst API_BASE = \"/plugins/signalk-edge-link\";\r\n\r\n// ── Stable ID helper ──────────────────────────────────────────────────────────\r\n// Each connection object carries a frontend-only `_id` for use as React key.\r\n// It is stripped before the array is POSTed to the backend.\r\n\r\nlet _idSeq = 0;\r\nfunction makeId(): string { return `skel-${Date.now()}-${++_idSeq}`; }\r\n\r\n// ── Types ─────────────────────────────────────────────────────────────────────\r\n\r\ninterface ConnectionData {\r\n _id: string;\r\n name?: string;\r\n serverType?: string;\r\n udpPort?: number;\r\n secretKey?: string;\r\n stretchAsciiKey?: boolean;\r\n useMsgpack?: boolean;\r\n usePathDictionary?: boolean;\r\n enableNotifications?: boolean;\r\n protocolVersion?: number;\r\n udpAddress?: string;\r\n helloMessageSender?: number;\r\n testAddress?: string;\r\n testPort?: number;\r\n pingIntervalTime?: number;\r\n [key: string]: unknown;\r\n}\r\n\r\ninterface SaveStatus {\r\n type: \"saving\" | \"success\" | \"error\";\r\n message: string;\r\n}\r\n\r\n// ── Default config factories ──────────────────────────────────────────────────\r\n\r\nfunction defaultClientConnection(name?: string): ConnectionData {\r\n return {\r\n _id: makeId(),\r\n name: name || \"client\",\r\n serverType: \"client\",\r\n udpPort: 4446,\r\n secretKey: \"\",\r\n stretchAsciiKey: false,\r\n useMsgpack: false,\r\n usePathDictionary: false,\r\n enableNotifications: false,\r\n protocolVersion: 1,\r\n udpAddress: \"127.0.0.1\",\r\n helloMessageSender: 60,\r\n testAddress: \"127.0.0.1\",\r\n testPort: 80,\r\n pingIntervalTime: 1\r\n };\r\n}\r\n\r\nfunction defaultServerConnection(name?: string): ConnectionData {\r\n return {\r\n _id: makeId(),\r\n name: name || \"server\",\r\n serverType: \"server\",\r\n udpPort: 4446,\r\n secretKey: \"\",\r\n stretchAsciiKey: false,\r\n useMsgpack: false,\r\n usePathDictionary: false,\r\n protocolVersion: 1\r\n };\r\n}\r\n\r\n/** Attach a stable _id to loaded connections that don't already have one. */\r\nfunction withId(conn: Omit<ConnectionData, \"_id\"> & { _id?: string }): ConnectionData {\r\n return conn._id ? (conn as ConnectionData) : { ...conn, _id: makeId() };\r\n}\r\n\r\n// Fill schema defaults into loaded form data so RJSF has nothing to augment on\r\n// mount — otherwise RJSF fires a synthetic onChange for every field that is\r\n// defined in the schema but absent from the persisted config (e.g.\r\n// stretchAsciiKey on pre-existing connections), which would trip the dirty flag\r\n// and surface \"Unsaved changes\" immediately after a fresh load.\r\nfunction withSchemaDefaults(conn: ConnectionData): ConnectionData {\r\n const isClient = conn.serverType !== \"server\";\r\n const schema = buildWebappConnectionSchema(isClient, conn.protocolVersion) as RJSFSchema;\r\n const { _id, ...formData } = conn;\r\n const enriched = getDefaultFormState(validator, schema, formData) as Record<string, unknown>;\r\n return { ...(enriched as Omit<ConnectionData, \"_id\">), _id };\r\n}\r\n\r\n// Deep equality that is insensitive to key insertion order (unlike\r\n// JSON.stringify). Used to decide whether an RJSF onChange carries a real\r\n// field-level difference.\r\nfunction stableStringify(value: unknown): string {\r\n if (value === null || typeof value !== \"object\") { return JSON.stringify(value); }\r\n if (Array.isArray(value)) {\r\n return \"[\" + value.map(stableStringify).join(\",\") + \"]\";\r\n }\r\n const obj = value as Record<string, unknown>;\r\n const keys = Object.keys(obj).sort();\r\n return \"{\" + keys.map((k) => JSON.stringify(k) + \":\" + stableStringify(obj[k])).join(\",\") + \"}\";\r\n}\r\n\r\nfunction connectionsEqual(a: Record<string, unknown>, b: Record<string, unknown>): boolean {\r\n const aKeys = Object.keys(a);\r\n const bKeys = Object.keys(b);\r\n if (aKeys.length !== bKeys.length) { return false; }\r\n for (const k of aKeys) {\r\n if (!Object.prototype.hasOwnProperty.call(b, k)) { return false; }\r\n const av = a[k];\r\n const bv = b[k];\r\n if (av === bv) { continue; }\r\n if (av !== null && bv !== null && typeof av === \"object\" && typeof bv === \"object\") {\r\n if (stableStringify(av) !== stableStringify(bv)) { return false; }\r\n continue;\r\n }\r\n return false;\r\n }\r\n return true;\r\n}\r\n\r\n// ── Schema ────────────────────────────────────────────────────────────────────\r\n// Single source of truth for field definitions: src/shared/connection-schema.ts\r\n// (also consumed by plugin.schema in src/index.ts).\r\n\r\nconst uiSchemaClient: UiSchema = {\r\n \"ui:order\": [\r\n \"name\", \"serverType\", \"udpAddress\", \"udpPort\", \"secretKey\", \"stretchAsciiKey\", \"protocolVersion\",\r\n \"useMsgpack\", \"usePathDictionary\", \"testAddress\", \"testPort\", \"pingIntervalTime\",\r\n \"helloMessageSender\", \"heartbeatInterval\", \"reliability\", \"congestionControl\", \"bonding\", \"enableNotifications\", \"alertThresholds\"\r\n ],\r\n secretKey: { \"ui:widget\": \"password\", \"ui:help\": \"Use 32-character ASCII, 64-character hex, or 44-character base64\" },\r\n stretchAsciiKey: { \"ui:help\": \"Only applies to 32-char ASCII keys. Must match on both peers.\" },\r\n serverType: { \"ui:widget\": \"select\" },\r\n reliability: {\r\n \"ui:classNames\": \"skel-optional-group\"\r\n },\r\n congestionControl: {\r\n \"ui:classNames\": \"skel-optional-group\"\r\n },\r\n bonding: {\r\n \"ui:classNames\": \"skel-optional-group\"\r\n },\r\n alertThresholds: {\r\n \"ui:classNames\": \"skel-optional-group\"\r\n }\r\n};\r\n\r\nconst uiSchemaServer: UiSchema = {\r\n \"ui:order\": [\r\n \"name\", \"serverType\", \"udpPort\", \"secretKey\", \"stretchAsciiKey\", \"useMsgpack\", \"usePathDictionary\",\r\n \"protocolVersion\", \"reliability\"\r\n ],\r\n secretKey: { \"ui:widget\": \"password\", \"ui:help\": \"Use 32-character ASCII, 64-character hex, or 44-character base64\" },\r\n stretchAsciiKey: { \"ui:help\": \"Only applies to 32-char ASCII keys. Must match on both peers.\" },\r\n serverType: { \"ui:widget\": \"select\" }\r\n};\r\n\r\n// Shared fields preserved when the user toggles server <-> client mode\r\nconst SHARED_FIELDS = [\"name\", \"udpPort\", \"secretKey\", \"stretchAsciiKey\", \"useMsgpack\", \"usePathDictionary\", \"protocolVersion\"];\r\n\r\n// ── Styles ────────────────────────────────────────────────────────────────────\r\n// Using `skel-` prefix (Signal K Edge Link) to avoid collisions with other\r\n// plugins that may inject CSS into the same admin panel page.\r\n\r\nconst css = `\r\n.skel-config { font-family: inherit; }\r\n.skel-dirty-banner {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 8px 14px;\r\n background: #fff3cd;\r\n color: #664d03;\r\n border: 1px solid #ffe69c;\r\n border-radius: 4px;\r\n margin-bottom: 12px;\r\n font-size: 0.88rem;\r\n}\r\n.skel-card {\r\n border: 1px solid #dee2e6;\r\n border-radius: 6px;\r\n margin-bottom: 12px;\r\n overflow: hidden;\r\n}\r\n.skel-card-header {\r\n display: flex;\r\n align-items: center;\r\n padding: 10px 14px;\r\n background: #f8f9fa;\r\n cursor: pointer;\r\n user-select: none;\r\n gap: 10px;\r\n}\r\n.skel-card-header:hover { background: #e9ecef; }\r\n.skel-badge {\r\n display: inline-block;\r\n padding: 2px 8px;\r\n border-radius: 12px;\r\n font-size: 0.75rem;\r\n font-weight: 600;\r\n text-transform: uppercase;\r\n letter-spacing: 0.03em;\r\n}\r\n.skel-badge-server { background: #cfe2ff; color: #084298; }\r\n.skel-badge-client { background: #d1e7dd; color: #0a3622; }\r\n.skel-card-title { font-weight: 600; flex: 1; }\r\n.skel-expand-icon { font-size: 0.8rem; color: #6c757d; }\r\n.skel-btn-remove {\r\n background: none;\r\n border: 1px solid #dc3545;\r\n color: #dc3545;\r\n border-radius: 4px;\r\n padding: 2px 8px;\r\n font-size: 0.8rem;\r\n cursor: pointer;\r\n}\r\n.skel-btn-remove:hover { background: #dc3545; color: white; }\r\n.skel-btn-remove:disabled { opacity: 0.4; cursor: default; border-color: #aaa; color: #aaa; }\r\n.skel-btn-remove:disabled:hover { background: none; }\r\n.skel-card-body { padding: 16px; border-top: 1px solid #dee2e6; }\r\n.skel-toolbar {\r\n display: flex;\r\n gap: 10px;\r\n align-items: center;\r\n margin-top: 16px;\r\n padding-top: 16px;\r\n border-top: 1px solid #dee2e6;\r\n flex-wrap: wrap;\r\n}\r\n.skel-btn {\r\n padding: 7px 16px;\r\n border-radius: 4px;\r\n font-size: 0.95rem;\r\n cursor: pointer;\r\n border: none;\r\n}\r\n.skel-btn-primary { background: #0d6efd; color: white; }\r\n.skel-btn-primary:hover { background: #0b5ed7; }\r\n.skel-btn-primary:disabled { background: #6c757d; cursor: default; }\r\n.skel-btn-secondary { background: white; color: #0d6efd; border: 1px solid #0d6efd; }\r\n.skel-btn-secondary:hover { background: #e7f0ff; }\r\n.skel-alert {\r\n padding: 10px 14px;\r\n border-radius: 4px;\r\n margin-bottom: 14px;\r\n font-size: 0.9rem;\r\n}\r\n.skel-alert-success { background: #d1e7dd; color: #0a3622; border: 1px solid #a3cfbb; }\r\n.skel-alert-error { background: #f8d7da; color: #58151c; border: 1px solid #f1aeb5; }\r\n.skel-alert-saving { background: #fff3cd; color: #664d03; border: 1px solid #ffe69c; }\r\n.skel-dup-warn { font-size: 0.8rem; color: #dc3545; margin-top: 4px; }\r\n.skel-plugin-settings {\r\n border: 1px solid #dee2e6;\r\n border-radius: 6px;\r\n margin-bottom: 20px;\r\n padding: 16px;\r\n background: #f8f9fa;\r\n}\r\n.skel-plugin-settings h3 {\r\n margin: 0 0 12px;\r\n font-size: 1rem;\r\n font-weight: 600;\r\n}\r\n.skel-field-group {\r\n margin-bottom: 14px;\r\n}\r\n.skel-field-group label {\r\n display: block;\r\n font-weight: 500;\r\n margin-bottom: 4px;\r\n font-size: 0.9rem;\r\n}\r\n.skel-field-group input[type=\"text\"],\r\n.skel-field-group input[type=\"password\"] {\r\n width: 100%;\r\n max-width: 420px;\r\n padding: 6px 10px;\r\n border: 1px solid #ced4da;\r\n border-radius: 4px;\r\n font-size: 0.9rem;\r\n}\r\n.skel-field-group input[type=\"checkbox\"] {\r\n margin-right: 6px;\r\n}\r\n.skel-field-desc {\r\n font-size: 0.8rem;\r\n color: #5c6773;\r\n margin-top: 3px;\r\n}\r\n.skel-config .field-description {\r\n color: #5c6773;\r\n font-size: 0.83rem;\r\n line-height: 1.35;\r\n}\r\n.skel-config legend,\r\n.skel-config label {\r\n line-height: 1.2;\r\n overflow-wrap: anywhere;\r\n}\r\n.skel-optional-group {\r\n margin-top: 12px;\r\n border: 1px dashed #ccd5df;\r\n border-radius: 6px;\r\n padding: 10px 12px 4px;\r\n background: #fbfcfe;\r\n}\r\n.skel-optional-group legend {\r\n font-size: 0.92rem;\r\n margin-bottom: 6px;\r\n}\r\n.skel-optional-group .form-group {\r\n margin-bottom: 10px;\r\n}\r\n.skel-optional-group .form-control {\r\n max-width: 340px;\r\n}\r\n`;\r\n\r\n// ── ConnectionCard ────────────────────────────────────────────────────────────\r\n\r\ninterface ConnectionCardProps {\r\n conn: ConnectionData;\r\n index: number;\r\n totalCount: number;\r\n expanded: boolean;\r\n onToggle: () => void;\r\n onChange: (data: ConnectionData) => void;\r\n onRemove: () => void;\r\n}\r\n\r\nfunction ConnectionCard({ conn, index, totalCount, expanded, onToggle, onChange, onRemove }: ConnectionCardProps) {\r\n const isClient = conn.serverType !== \"server\";\r\n const schema = buildWebappConnectionSchema(isClient, conn.protocolVersion) as RJSFSchema;\r\n const uiSchema = isClient ? uiSchemaClient : uiSchemaServer;\r\n const modeLabel = isClient ? \"Client\" : \"Server\";\r\n const displayName = (conn.name || `Connection ${index + 1}`).trim();\r\n\r\n function handleFormChange(e: any) {\r\n const next: ConnectionData = e.formData;\r\n if (next.serverType !== conn.serverType) {\r\n const base = next.serverType === \"server\"\r\n ? defaultServerConnection(next.name)\r\n : defaultClientConnection(next.name);\r\n const merged: ConnectionData = { ...base, _id: conn._id };\r\n for (const k of SHARED_FIELDS) {\r\n if (next[k] !== undefined) { (merged as Record<string, unknown>)[k] = next[k]; }\r\n }\r\n merged.serverType = next.serverType;\r\n onChange(merged);\r\n return;\r\n }\r\n // Skip propagation when the incoming form data is identical to the current\r\n // connection — RJSF can fire onChange with no effective diff (e.g. after\r\n // internal re-renders), and we do not want that to trip the dirty flag.\r\n // Order-insensitive compare so a reshuffled-but-equivalent formData does\r\n // not look like a real edit.\r\n const proposed: ConnectionData = { ...next, _id: conn._id };\r\n const { _id: _aId, ...a } = proposed;\r\n const { _id: _bId, ...b } = conn;\r\n if (connectionsEqual(a, b)) { return; }\r\n onChange(proposed);\r\n }\r\n\r\n // Strip the frontend-only _id before passing to RJSF\r\n const { _id, ...formData } = conn;\r\n\r\n return (\r\n <div className=\"skel-card\">\r\n <div className=\"skel-card-header\" onClick={onToggle} role=\"button\" aria-expanded={expanded}>\r\n <span className={`skel-badge ${isClient ? \"skel-badge-client\" : \"skel-badge-server\"}`}>\r\n {modeLabel}\r\n </span>\r\n <span className=\"skel-card-title\">{displayName}</span>\r\n <span className=\"skel-expand-icon\">{expanded ? \"\\u25B2\" : \"\\u25BC\"}</span>\r\n <button\r\n className=\"skel-btn-remove\"\r\n disabled={totalCount <= 1}\r\n onClick={(e) => { e.stopPropagation(); onRemove(); }}\r\n title={totalCount <= 1 ? \"Cannot remove the only connection\" : \"Remove this connection\"}\r\n >\r\n Remove\r\n </button>\r\n </div>\r\n {expanded && (\r\n <div className=\"skel-card-body\">\r\n <Form\r\n schema={schema}\r\n uiSchema={uiSchema}\r\n formData={formData}\r\n validator={validator}\r\n onChange={handleFormChange}\r\n onSubmit={() => {}}\r\n liveValidate={false}\r\n >\r\n {/* Hide the default submit button – saving is done from the outer toolbar */}\r\n <div />\r\n </Form>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n\r\n// ── Main panel ────────────────────────────────────────────────────────────────\r\n\r\nfunction PluginConfigurationPanel(_props: Record<string, unknown>) {\r\n const [connections, setConnections] = useState<ConnectionData[]>([]);\r\n const [managementApiToken, setManagementApiToken] = useState<string>(\"\");\r\n const [requireManagementApiToken, setRequireManagementApiToken] = useState<boolean>(false);\r\n const [loading, setLoading] = useState(true);\r\n const [loadError, setLoadError] = useState<string | null>(null);\r\n const [saveStatus, setSaveStatus] = useState<SaveStatus | null>(null);\r\n const [inlineValidationMessage, setInlineValidationMessage] = useState<string | null>(null);\r\n const [expandedIndex, setExpandedIndex] = useState<number | null>(0);\r\n const [isDirty, setIsDirty] = useState(false);\r\n const savingRef = useRef(false);\r\n\r\n // ── Load config ─────────────────────────────────────────────────────────────\r\n useEffect(() => {\r\n async function load() {\r\n try {\r\n const res = await apiFetch(`${API_BASE}/plugin-config`);\r\n if (res.status === 401) {\r\n throw new Error(MANAGEMENT_TOKEN_ERROR_MESSAGE);\r\n }\r\n if (!res.ok) { throw new Error(`HTTP ${res.status}: ${res.statusText}`); }\r\n const body = await res.json();\r\n if (!body.success) { throw new Error(body.error || \"Failed to load configuration\"); }\r\n\r\n const cfg = body.configuration || {};\r\n let list: ConnectionData[];\r\n if (Array.isArray(cfg.connections) && cfg.connections.length > 0) {\r\n list = cfg.connections.map((c: Omit<ConnectionData, \"_id\">) => withSchemaDefaults(withId(c)));\r\n } else if (cfg.serverType) {\r\n list = [withSchemaDefaults(withId(cfg))];\r\n } else {\r\n list = [defaultClientConnection()];\r\n }\r\n setConnections(list);\r\n setManagementApiToken(typeof cfg.managementApiToken === \"string\" ? cfg.managementApiToken : \"\");\r\n setRequireManagementApiToken(cfg.requireManagementApiToken === true);\r\n setExpandedIndex(0);\r\n setIsDirty(false);\r\n } catch (err: unknown) {\r\n setLoadError(err instanceof Error ? err.message : String(err));\r\n } finally {\r\n setLoading(false);\r\n }\r\n }\r\n load();\r\n }, []);\r\n\r\n // ── Duplicate server-port detection ─────────────────────────────────────────\r\n const serverPorts = connections\r\n .filter((c) => c.serverType === \"server\")\r\n .map((c) => c.udpPort);\r\n const duplicatePortSet = new Set(\r\n serverPorts.filter((p, i) => serverPorts.indexOf(p) !== i)\r\n );\r\n\r\n // ── Handlers ─────────────────────────────────────────────────────────────────\r\n function markDirty() {\r\n setIsDirty(true);\r\n setSaveStatus(null);\r\n setInlineValidationMessage(null);\r\n }\r\n\r\n function updateConnection(idx: number, data: ConnectionData) {\r\n setConnections((prev) => prev.map((c, i) => (i === idx ? data : c)));\r\n markDirty();\r\n }\r\n\r\n function addServer() {\r\n setConnections((prev) => {\r\n const next = [...prev, defaultServerConnection(`server-${prev.length + 1}`)];\r\n setExpandedIndex(next.length - 1);\r\n return next;\r\n });\r\n markDirty();\r\n }\r\n\r\n function addClient() {\r\n setConnections((prev) => {\r\n const next = [...prev, defaultClientConnection(`client-${prev.length + 1}`)];\r\n setExpandedIndex(next.length - 1);\r\n return next;\r\n });\r\n markDirty();\r\n }\r\n\r\n function removeConnection(idx: number) {\r\n setConnections((prev) => {\r\n if (prev.length <= 1) return prev;\r\n const next = prev.filter((_, i) => i !== idx);\r\n setExpandedIndex((prevExpanded) => (prevExpanded !== null && prevExpanded >= idx && prevExpanded > 0 ? prevExpanded - 1 : prevExpanded));\r\n return next;\r\n });\r\n markDirty();\r\n }\r\n\r\n function toggleExpand(idx: number) {\r\n setExpandedIndex((prev) => (prev === idx ? null : idx));\r\n }\r\n\r\n const handleSave = useCallback(async () => {\r\n if (savingRef.current) { return; }\r\n if (connections.length === 0) {\r\n setInlineValidationMessage(\"At least one connection is required before saving.\");\r\n setSaveStatus({\r\n type: \"error\",\r\n message: \"Cannot save an empty configuration. Add at least one connection.\"\r\n });\r\n return;\r\n }\r\n\r\n setInlineValidationMessage(null);\r\n if (duplicatePortSet.size > 0) {\r\n setSaveStatus({\r\n type: \"error\",\r\n message: `Duplicate server ports detected: ${[...duplicatePortSet].join(\", \")}. Each server must use a unique UDP port.`\r\n });\r\n return;\r\n }\r\n\r\n savingRef.current = true;\r\n setSaveStatus({ type: \"saving\", message: \"Saving configuration...\" });\r\n try {\r\n const payload = connections.map(({ _id, ...rest }) => rest);\r\n const res = await apiFetch(`${API_BASE}/plugin-config`, {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: JSON.stringify({\r\n connections: payload,\r\n managementApiToken: managementApiToken,\r\n requireManagementApiToken: requireManagementApiToken\r\n })\r\n });\r\n if (res.status === 401) {\r\n throw new Error(MANAGEMENT_TOKEN_ERROR_MESSAGE);\r\n }\r\n const body = await res.json();\r\n if (res.ok && body.success) {\r\n setSaveStatus({ type: \"success\", message: body.message || \"Configuration saved. Plugin restarting...\" });\r\n setIsDirty(false);\r\n } else {\r\n throw new Error(body.error || \"Failed to save\");\r\n }\r\n } catch (err: unknown) {\r\n setSaveStatus({ type: \"error\", message: err instanceof Error ? err.message : String(err) });\r\n } finally {\r\n savingRef.current = false;\r\n }\r\n }, [connections, duplicatePortSet, managementApiToken, requireManagementApiToken]);\r\n\r\n // ── Render ────────────────────────────────────────────────────────────────────\r\n if (loading) {\r\n return <div style={{ padding: \"20px\", textAlign: \"center\" }}>Loading configuration...</div>;\r\n }\r\n\r\n if (loadError) {\r\n return (\r\n <div style={{ padding: \"20px\" }}>\r\n <div className=\"skel-alert skel-alert-error\">\r\n <strong>Error loading configuration:</strong> {loadError}\r\n </div>\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <div className=\"skel-config\">\r\n <style>{css}</style>\r\n\r\n {isDirty && saveStatus?.type !== \"saving\" && (\r\n <div className=\"skel-dirty-banner\">\r\n <span>&#9888;</span>\r\n <span>You have unsaved changes.</span>\r\n </div>\r\n )}\r\n\r\n {saveStatus && (\r\n <div className={`skel-alert skel-alert-${saveStatus.type === \"saving\" ? \"saving\" : saveStatus.type === \"success\" ? \"success\" : \"error\"}`}>\r\n {saveStatus.message}\r\n </div>\r\n )}\r\n\r\n {/* Plugin-level security settings */}\r\n <div className=\"skel-plugin-settings\">\r\n <h3>Plugin Security Settings</h3>\r\n <div className=\"skel-field-group\">\r\n <label htmlFor=\"skel-mgmt-token\">Management API Token</label>\r\n <input\r\n id=\"skel-mgmt-token\"\r\n type=\"password\"\r\n value={managementApiToken}\r\n placeholder=\"Leave empty for open access\"\r\n onChange={(e) => { setManagementApiToken(e.target.value); markDirty(); }}\r\n autoComplete=\"new-password\"\r\n />\r\n <div className=\"skel-field-desc\">\r\n Shared secret to protect the management API endpoints. Strongly recommended for\r\n production. Can also be set via the{\" \"}\r\n <code>SIGNALK_EDGE_LINK_MANAGEMENT_TOKEN</code> environment variable (env var takes\r\n priority). Leave empty to allow open access.\r\n </div>\r\n </div>\r\n <div className=\"skel-field-group\">\r\n <label>\r\n <input\r\n type=\"checkbox\"\r\n checked={requireManagementApiToken}\r\n onChange={(e) => { setRequireManagementApiToken(e.target.checked); markDirty(); }}\r\n />\r\n Require Management API Token\r\n </label>\r\n <div className=\"skel-field-desc\">\r\n When enabled, all management API requests are rejected if no token is configured\r\n (fail-closed). When disabled, requests are allowed if no token is set (open access).\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {connections.map((conn, idx) => (\r\n <div key={conn._id}>\r\n <ConnectionCard\r\n conn={conn}\r\n index={idx}\r\n totalCount={connections.length}\r\n expanded={expandedIndex === idx}\r\n onToggle={() => toggleExpand(idx)}\r\n onChange={(data: ConnectionData) => updateConnection(idx, data)}\r\n onRemove={() => removeConnection(idx)}\r\n />\r\n {conn.serverType === \"server\" && duplicatePortSet.has(conn.udpPort) && (\r\n <div className=\"skel-dup-warn\">\r\n Port {conn.udpPort} is used by multiple server connections. Each server requires a unique port.\r\n </div>\r\n )}\r\n </div>\r\n ))}\r\n\r\n <div className=\"skel-toolbar\">\r\n <button className=\"skel-btn skel-btn-secondary\" onClick={addServer}>\r\n + Add Server\r\n </button>\r\n <button className=\"skel-btn skel-btn-secondary\" onClick={addClient}>\r\n + Add Client\r\n </button>\r\n <button\r\n className=\"skel-btn skel-btn-primary\"\r\n onClick={handleSave}\r\n disabled={(saveStatus && saveStatus.type === \"saving\") || connections.length === 0}\r\n >\r\n {isDirty ? \"Save Changes\" : \"Save Configuration\"}\r\n </button>\r\n {inlineValidationMessage && (\r\n <span style={{ color: \"#dc3545\", fontSize: \"0.85rem\", fontWeight: 500 }}>\r\n {inlineValidationMessage}\r\n </span>\r\n )}\r\n <span style={{ fontSize: \"0.85rem\", color: \"#6c757d\" }}>\r\n {connections.length} connection{connections.length !== 1 ? \"s\" : \"\"}\r\n {\" \\u00B7 \"}\r\n {connections.filter((c) => c.serverType === \"server\").length} server\r\n {connections.filter((c) => c.serverType === \"server\").length !== 1 ? \"s\" : \"\"}\r\n {\", \"}\r\n {connections.filter((c) => c.serverType !== \"server\").length} client\r\n {connections.filter((c) => c.serverType !== \"server\").length !== 1 ? \"s\" : \"\"}\r\n </span>\r\n </div>\r\n </div>\r\n );\r\n}\r\n\r\nexport default PluginConfigurationPanel;\r\n"],"names":["MANAGEMENT_TOKEN_ERROR_MESSAGE","DEFAULT_AUTH_CONFIG","token","localStorageKey","queryParam","includeTokenInQuery","headerMode","apiFetch","input","init","config","window","runtime","__EDGE_LINK_AUTH__","readRuntimeAuthConfig","String","trim","tokenFromQuery","URLSearchParams","location","search","get","localStorage","tokenFromStorage","getItem","resolveToken","headers","Headers","normalizedMode","toLowerCase","set","attachAuthHeaders","fetch","commonConnectionProperties","name","type","title","description","default","maxLength","serverType","oneOf","const","udpPort","minimum","maximum","secretKey","minLength","pattern","stretchAsciiKey","toLocaleString","useMsgpack","usePathDictionary","protocolVersion","clientTransportProperties","udpAddress","helloMessageSender","testAddress","testPort","pingIntervalTime","heartbeatInterval","clientReliabilityProperty","properties","retransmitQueueSize","maxRetransmits","retransmitMaxAge","retransmitMinAge","retransmitRttMultiplier","ackIdleDrainAge","forceDrainAfterAckIdle","forceDrainAfterMs","recoveryBurstEnabled","recoveryBurstSize","recoveryBurstIntervalMs","recoveryAckGapMs","serverReliabilityProperty","ackInterval","ackResendInterval","nakTimeout","congestionControlProperty","enabled","targetRTT","nominalDeltaTimer","minDeltaTimer","maxDeltaTimer","bondingProperty","mode","primary","address","port","interface","backup","failover","rttThreshold","lossThreshold","healthCheckInterval","failbackDelay","heartbeatTimeout","enableNotificationsProperty","alertThresholdsProperty","rtt","warning","critical","packetLoss","retransmitRate","jitter","queueDepth","buildWebappConnectionSchema","isClient","isReliableProtocol","Number","props","required","Object","assign","enableNotifications","push","reliability","congestionControl","bonding","alertThresholds","API_BASE","_idSeq","makeId","Date","now","defaultClientConnection","_id","defaultServerConnection","withId","conn","withSchemaDefaults","schema","formData","stableStringify","value","JSON","stringify","Array","isArray","map","join","obj","keys","sort","k","uiSchemaClient","uiSchemaServer","SHARED_FIELDS","ConnectionCard","index","totalCount","expanded","onToggle","onChange","onRemove","uiSchema","modeLabel","displayName","className","onClick","role","disabled","e","stopPropagation","validator","next","merged","undefined","proposed","_aId","a","_bId","b","aKeys","bKeys","length","prototype","hasOwnProperty","call","av","bv","connectionsEqual","onSubmit","liveValidate","_props","connections","setConnections","useState","managementApiToken","setManagementApiToken","requireManagementApiToken","setRequireManagementApiToken","loading","setLoading","loadError","setLoadError","saveStatus","setSaveStatus","inlineValidationMessage","setInlineValidationMessage","expandedIndex","setExpandedIndex","isDirty","setIsDirty","savingRef","useRef","useEffect","async","res","status","Error","ok","statusText","body","json","success","error","cfg","configuration","list","c","err","message","load","serverPorts","filter","duplicatePortSet","Set","p","i","indexOf","markDirty","handleSave","useCallback","current","size","payload","rest","method","style","padding","textAlign","htmlFor","id","placeholder","target","autoComplete","checked","idx","key","prev","toggleExpand","data","updateConnection","_","prevExpanded","removeConnection","has","color","fontSize","fontWeight"],"sourceRoot":""}
@@ -1,2 +1,2 @@
1
- var signalk_edge_link;(()=>{"use strict";var e,r,t,n,a,o,i,l,u,s,f,d,c,p,h,v,g,m,b,y,k,w={805(e,r,t){var n={"./PluginConfigurationPanel":()=>Promise.all([t.e(277),t.e(982)]).then(()=>()=>t(4353))},a=(e,r)=>(t.R=r,r=t.o(n,e)?n[e]():Promise.resolve().then(()=>{throw new Error('Module "'+e+'" does not exist in container.')}),t.R=void 0,r),o=(e,r)=>{if(t.S){var n="default",a=t.S[n];if(a&&a!==e)throw new Error("Container initialization failed as it has already been initialized with a different share scope");return t.S[n]=e,t.I(n,r)}};t.d(r,{get:()=>a,init:()=>o})}},S={};function j(e){var r=S[e];if(void 0!==r)return r.exports;var t=S[e]={id:e,loaded:!1,exports:{}};return w[e](t,t.exports,j),t.loaded=!0,t.exports}j.m=w,j.c=S,j.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return j.d(r,{a:r}),r},j.d=(e,r)=>{for(var t in r)j.o(r,t)&&!j.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},j.f={},j.e=e=>Promise.all(Object.keys(j.f).reduce((r,t)=>(j.f[t](e,r),r),[])),j.u=e=>e+"."+{277:"99e19dcb5b778c964ace",540:"70a0bc6aadb412703390",982:"b207a377ed6542e2fb4a"}[e]+".js",j.miniCssF=e=>{},j.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),j.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),e={},r="signalk-edge-link:",j.l=(t,n,a,o)=>{if(e[t])e[t].push(n);else{var i,l;if(void 0!==a)for(var u=document.getElementsByTagName("script"),s=0;s<u.length;s++){var f=u[s];if(f.getAttribute("src")==t||f.getAttribute("data-webpack")==r+a){i=f;break}}i||(l=!0,(i=document.createElement("script")).charset="utf-8",j.nc&&i.setAttribute("nonce",j.nc),i.setAttribute("data-webpack",r+a),i.src=t),e[t]=[n];var d=(r,n)=>{i.onerror=i.onload=null,clearTimeout(c);var a=e[t];if(delete e[t],i.parentNode&&i.parentNode.removeChild(i),a&&a.forEach(e=>e(n)),r)return r(n)},c=setTimeout(d.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=d.bind(null,i.onerror),i.onload=d.bind(null,i.onload),l&&document.head.appendChild(i)}},j.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},j.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{j.S={};var e={},r={};j.I=(t,n)=>{n||(n=[]);var a=r[t];if(a||(a=r[t]={}),!(n.indexOf(a)>=0)){if(n.push(a),e[t])return e[t];j.o(j.S,t)||(j.S[t]={});var o=j.S[t],i="signalk-edge-link",l=[];return"default"===t&&((e,r,t,n)=>{var a=o[e]=o[e]||{},l=a[r];(!l||!l.loaded&&(1!=!l.eager?n:i>l.from))&&(a[r]={get:()=>j.e(540).then(()=>()=>j(6540)),from:i,eager:!1})})("react","16.14.0"),e[t]=l.length?Promise.all(l).then(()=>e[t]=1):1}}})(),(()=>{var e;j.g.importScripts&&(e=j.g.location+"");var r=j.g.document;if(!e&&r&&(r.currentScript&&"SCRIPT"===r.currentScript.tagName.toUpperCase()&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var n=t.length-1;n>-1&&(!e||!/^http(s?):/.test(e));)e=t[n--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),j.p=e})(),t=e=>{var r=e=>e.split(".").map(e=>+e==e?+e:e),t=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(e),n=t[1]?r(t[1]):[];return t[2]&&(n.length++,n.push.apply(n,r(t[2]))),t[3]&&(n.push([]),n.push.apply(n,r(t[3]))),n},n=(e,r)=>{e=t(e),r=t(r);for(var n=0;;){if(n>=e.length)return n<r.length&&"u"!=(typeof r[n])[0];var a=e[n],o=(typeof a)[0];if(n>=r.length)return"u"==o;var i=r[n],l=(typeof i)[0];if(o!=l)return"o"==o&&"n"==l||"s"==l||"u"==o;if("o"!=o&&"u"!=o&&a!=i)return a<i;n++}},a=e=>{var r=e[0],t="";if(1===e.length)return"*";if(r+.5){t+=0==r?">=":-1==r?"<":1==r?"^":2==r?"~":r>0?"=":"!=";for(var n=1,o=1;o<e.length;o++)n--,t+="u"==(typeof(l=e[o]))[0]?"-":(n>0?".":"")+(n=2,l);return t}var i=[];for(o=1;o<e.length;o++){var l=e[o];i.push(0===l?"not("+u()+")":1===l?"("+u()+" || "+u()+")":2===l?i.pop()+" "+i.pop():a(l))}return u();function u(){return i.pop().replace(/^\((.+)\)$/,"$1")}},o=(e,r)=>{if(0 in e){r=t(r);var n=e[0],a=n<0;a&&(n=-n-1);for(var i=0,l=1,u=!0;;l++,i++){var s,f,d=l<e.length?(typeof e[l])[0]:"";if(i>=r.length||"o"==(f=(typeof(s=r[i]))[0]))return!u||("u"==d?l>n&&!a:""==d!=a);if("u"==f){if(!u||"u"!=d)return!1}else if(u)if(d==f)if(l<=n){if(s!=e[l])return!1}else{if(a?s>e[l]:s<e[l])return!1;s!=e[l]&&(u=!1)}else if("s"!=d&&"n"!=d){if(a||l<=n)return!1;u=!1,l--}else{if(l<=n||f<d!=a)return!1;u=!1}else"s"!=d&&"n"!=d&&(u=!1,l--)}}var c=[],p=c.pop.bind(c);for(i=1;i<e.length;i++){var h=e[i];c.push(1==h?p()|p():2==h?p()&p():h?o(h,r):!p())}return!!p()},i=(e,r)=>e&&j.o(e,r),l=e=>(e.loaded=1,e.get()),u=e=>Object.keys(e).reduce((r,t)=>(e[t].eager&&(r[t]=e[t]),r),{}),s=(e,r,t)=>{var a=t?u(e[r]):e[r];return(r=Object.keys(a).reduce((e,r)=>!e||n(e,r)?r:e,0))&&a[r]},f=(e,r,t,a)=>{var i=a?u(e[r]):e[r];return(r=Object.keys(i).reduce((e,r)=>!o(t,r)||e&&!n(e,r)?e:r,0))&&i[r]},d=(e,r,t,n,o)=>{var i=e[t];return"No satisfying version ("+a(n)+")"+(o?" for eager consumption":"")+" of shared module "+t+" found in shared scope "+r+".\nAvailable versions: "+Object.keys(i).map(e=>e+" from "+i[e].from).join(", ")},c=e=>{throw new Error(e)},h=(e,r,t)=>t?t():((e,r)=>c("Shared module "+r+" doesn't exist in shared scope "+e))(e,r),v=(p=e=>function(r,t,n,a,o){var i=j.I(r);return i&&i.then&&!n?i.then(e.bind(e,r,j.S[r],t,!1,a,o)):e(r,j.S[r],t,n,a,o)})((e,r,t,n,a)=>i(r,t)?l(s(r,t,n)):h(e,t,a)),g=p((e,r,t,n,a,o)=>{if(!i(r,t))return h(e,t,o);var u=f(r,t,a,n);return u?l(u):o?o():void c(d(r,e,t,a,n))}),m={},b={4147:()=>g("default","react",!1,[1,16,13,1],()=>j.e(540).then(()=>()=>j(6540))),4167:()=>v("default","react",!1,()=>j.e(540).then(()=>()=>j(6540))),7920:()=>g("default","react",!1,[,[0,17],[1,16,14,0],1],()=>j.e(540).then(()=>()=>j(6540))),8271:()=>g("default","react",!1,[0,0,14,0],()=>j.e(540).then(()=>()=>j(6540)))},y={982:[4147,4167,7920,8271]},k={},j.f.consumes=(e,r)=>{j.o(y,e)&&y[e].forEach(e=>{if(j.o(m,e))return r.push(m[e]);if(!k[e]){var t=r=>{m[e]=0,j.m[e]=t=>{delete j.c[e],t.exports=r()}};k[e]=!0;var n=r=>{delete m[e],j.m[e]=t=>{throw delete j.c[e],r}};try{var a=b[e]();a.then?r.push(m[e]=a.then(t).catch(n)):t(a)}catch(e){n(e)}}})},(()=>{var e={981:0};j.f.j=(r,t)=>{var n=j.o(e,r)?e[r]:void 0;if(0!==n)if(n)t.push(n[2]);else{var a=new Promise((t,a)=>n=e[r]=[t,a]);t.push(n[2]=a);var o=j.p+j.u(r),i=new Error;j.l(o,t=>{if(j.o(e,r)&&(0!==(n=e[r])&&(e[r]=void 0),n)){var a=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;i.message="Loading chunk "+r+" failed.\n("+a+": "+o+")",i.name="ChunkLoadError",i.type=a,i.request=o,n[1](i)}},"chunk-"+r,r)}};var r=(r,t)=>{var n,a,[o,i,l]=t,u=0;if(o.some(r=>0!==e[r])){for(n in i)j.o(i,n)&&(j.m[n]=i[n]);l&&l(j)}for(r&&r(t);u<o.length;u++)a=o[u],j.o(e,a)&&e[a]&&e[a][0](),e[a]=0},t=self.webpackChunksignalk_edge_link=self.webpackChunksignalk_edge_link||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})();var P=j(805);signalk_edge_link=P})();
1
+ var signalk_edge_link;(()=>{"use strict";var e,r,t,n,a,o,i,l,u,s,f,d,c,p,h,v,g,m,b,y,k,w={805(e,r,t){var n={"./PluginConfigurationPanel":()=>Promise.all([t.e(277),t.e(982)]).then(()=>()=>t(4353))},a=(e,r)=>(t.R=r,r=t.o(n,e)?n[e]():Promise.resolve().then(()=>{throw new Error('Module "'+e+'" does not exist in container.')}),t.R=void 0,r),o=(e,r)=>{if(t.S){var n="default",a=t.S[n];if(a&&a!==e)throw new Error("Container initialization failed as it has already been initialized with a different share scope");return t.S[n]=e,t.I(n,r)}};t.d(r,{get:()=>a,init:()=>o})}},S={};function j(e){var r=S[e];if(void 0!==r)return r.exports;var t=S[e]={id:e,loaded:!1,exports:{}};return w[e](t,t.exports,j),t.loaded=!0,t.exports}j.m=w,j.c=S,j.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return j.d(r,{a:r}),r},j.d=(e,r)=>{for(var t in r)j.o(r,t)&&!j.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},j.f={},j.e=e=>Promise.all(Object.keys(j.f).reduce((r,t)=>(j.f[t](e,r),r),[])),j.u=e=>e+"."+{277:"99e19dcb5b778c964ace",540:"70a0bc6aadb412703390",982:"63949a2b2f6c5854e034"}[e]+".js",j.miniCssF=e=>{},j.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),j.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),e={},r="signalk-edge-link:",j.l=(t,n,a,o)=>{if(e[t])e[t].push(n);else{var i,l;if(void 0!==a)for(var u=document.getElementsByTagName("script"),s=0;s<u.length;s++){var f=u[s];if(f.getAttribute("src")==t||f.getAttribute("data-webpack")==r+a){i=f;break}}i||(l=!0,(i=document.createElement("script")).charset="utf-8",j.nc&&i.setAttribute("nonce",j.nc),i.setAttribute("data-webpack",r+a),i.src=t),e[t]=[n];var d=(r,n)=>{i.onerror=i.onload=null,clearTimeout(c);var a=e[t];if(delete e[t],i.parentNode&&i.parentNode.removeChild(i),a&&a.forEach(e=>e(n)),r)return r(n)},c=setTimeout(d.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=d.bind(null,i.onerror),i.onload=d.bind(null,i.onload),l&&document.head.appendChild(i)}},j.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},j.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{j.S={};var e={},r={};j.I=(t,n)=>{n||(n=[]);var a=r[t];if(a||(a=r[t]={}),!(n.indexOf(a)>=0)){if(n.push(a),e[t])return e[t];j.o(j.S,t)||(j.S[t]={});var o=j.S[t],i="signalk-edge-link",l=[];return"default"===t&&((e,r,t,n)=>{var a=o[e]=o[e]||{},l=a[r];(!l||!l.loaded&&(1!=!l.eager?n:i>l.from))&&(a[r]={get:()=>j.e(540).then(()=>()=>j(6540)),from:i,eager:!1})})("react","16.14.0"),e[t]=l.length?Promise.all(l).then(()=>e[t]=1):1}}})(),(()=>{var e;j.g.importScripts&&(e=j.g.location+"");var r=j.g.document;if(!e&&r&&(r.currentScript&&"SCRIPT"===r.currentScript.tagName.toUpperCase()&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var n=t.length-1;n>-1&&(!e||!/^http(s?):/.test(e));)e=t[n--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),j.p=e})(),t=e=>{var r=e=>e.split(".").map(e=>+e==e?+e:e),t=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(e),n=t[1]?r(t[1]):[];return t[2]&&(n.length++,n.push.apply(n,r(t[2]))),t[3]&&(n.push([]),n.push.apply(n,r(t[3]))),n},n=(e,r)=>{e=t(e),r=t(r);for(var n=0;;){if(n>=e.length)return n<r.length&&"u"!=(typeof r[n])[0];var a=e[n],o=(typeof a)[0];if(n>=r.length)return"u"==o;var i=r[n],l=(typeof i)[0];if(o!=l)return"o"==o&&"n"==l||"s"==l||"u"==o;if("o"!=o&&"u"!=o&&a!=i)return a<i;n++}},a=e=>{var r=e[0],t="";if(1===e.length)return"*";if(r+.5){t+=0==r?">=":-1==r?"<":1==r?"^":2==r?"~":r>0?"=":"!=";for(var n=1,o=1;o<e.length;o++)n--,t+="u"==(typeof(l=e[o]))[0]?"-":(n>0?".":"")+(n=2,l);return t}var i=[];for(o=1;o<e.length;o++){var l=e[o];i.push(0===l?"not("+u()+")":1===l?"("+u()+" || "+u()+")":2===l?i.pop()+" "+i.pop():a(l))}return u();function u(){return i.pop().replace(/^\((.+)\)$/,"$1")}},o=(e,r)=>{if(0 in e){r=t(r);var n=e[0],a=n<0;a&&(n=-n-1);for(var i=0,l=1,u=!0;;l++,i++){var s,f,d=l<e.length?(typeof e[l])[0]:"";if(i>=r.length||"o"==(f=(typeof(s=r[i]))[0]))return!u||("u"==d?l>n&&!a:""==d!=a);if("u"==f){if(!u||"u"!=d)return!1}else if(u)if(d==f)if(l<=n){if(s!=e[l])return!1}else{if(a?s>e[l]:s<e[l])return!1;s!=e[l]&&(u=!1)}else if("s"!=d&&"n"!=d){if(a||l<=n)return!1;u=!1,l--}else{if(l<=n||f<d!=a)return!1;u=!1}else"s"!=d&&"n"!=d&&(u=!1,l--)}}var c=[],p=c.pop.bind(c);for(i=1;i<e.length;i++){var h=e[i];c.push(1==h?p()|p():2==h?p()&p():h?o(h,r):!p())}return!!p()},i=(e,r)=>e&&j.o(e,r),l=e=>(e.loaded=1,e.get()),u=e=>Object.keys(e).reduce((r,t)=>(e[t].eager&&(r[t]=e[t]),r),{}),s=(e,r,t)=>{var a=t?u(e[r]):e[r];return(r=Object.keys(a).reduce((e,r)=>!e||n(e,r)?r:e,0))&&a[r]},f=(e,r,t,a)=>{var i=a?u(e[r]):e[r];return(r=Object.keys(i).reduce((e,r)=>!o(t,r)||e&&!n(e,r)?e:r,0))&&i[r]},d=(e,r,t,n,o)=>{var i=e[t];return"No satisfying version ("+a(n)+")"+(o?" for eager consumption":"")+" of shared module "+t+" found in shared scope "+r+".\nAvailable versions: "+Object.keys(i).map(e=>e+" from "+i[e].from).join(", ")},c=e=>{throw new Error(e)},h=(e,r,t)=>t?t():((e,r)=>c("Shared module "+r+" doesn't exist in shared scope "+e))(e,r),v=(p=e=>function(r,t,n,a,o){var i=j.I(r);return i&&i.then&&!n?i.then(e.bind(e,r,j.S[r],t,!1,a,o)):e(r,j.S[r],t,n,a,o)})((e,r,t,n,a)=>i(r,t)?l(s(r,t,n)):h(e,t,a)),g=p((e,r,t,n,a,o)=>{if(!i(r,t))return h(e,t,o);var u=f(r,t,a,n);return u?l(u):o?o():void c(d(r,e,t,a,n))}),m={},b={4147:()=>g("default","react",!1,[1,16,13,1],()=>j.e(540).then(()=>()=>j(6540))),4167:()=>v("default","react",!1,()=>j.e(540).then(()=>()=>j(6540))),7920:()=>g("default","react",!1,[,[0,17],[1,16,14,0],1],()=>j.e(540).then(()=>()=>j(6540))),8271:()=>g("default","react",!1,[0,0,14,0],()=>j.e(540).then(()=>()=>j(6540)))},y={982:[4147,4167,7920,8271]},k={},j.f.consumes=(e,r)=>{j.o(y,e)&&y[e].forEach(e=>{if(j.o(m,e))return r.push(m[e]);if(!k[e]){var t=r=>{m[e]=0,j.m[e]=t=>{delete j.c[e],t.exports=r()}};k[e]=!0;var n=r=>{delete m[e],j.m[e]=t=>{throw delete j.c[e],r}};try{var a=b[e]();a.then?r.push(m[e]=a.then(t).catch(n)):t(a)}catch(e){n(e)}}})},(()=>{var e={981:0};j.f.j=(r,t)=>{var n=j.o(e,r)?e[r]:void 0;if(0!==n)if(n)t.push(n[2]);else{var a=new Promise((t,a)=>n=e[r]=[t,a]);t.push(n[2]=a);var o=j.p+j.u(r),i=new Error;j.l(o,t=>{if(j.o(e,r)&&(0!==(n=e[r])&&(e[r]=void 0),n)){var a=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;i.message="Loading chunk "+r+" failed.\n("+a+": "+o+")",i.name="ChunkLoadError",i.type=a,i.request=o,n[1](i)}},"chunk-"+r,r)}};var r=(r,t)=>{var n,a,[o,i,l]=t,u=0;if(o.some(r=>0!==e[r])){for(n in i)j.o(i,n)&&(j.m[n]=i[n]);l&&l(j)}for(r&&r(t);u<o.length;u++)a=o[u],j.o(e,a)&&e[a]&&e[a][0](),e[a]=0},t=self.webpackChunksignalk_edge_link=self.webpackChunksignalk_edge_link||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})();var P=j(805);signalk_edge_link=P})();
2
2
  //# sourceMappingURL=remoteEntry.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"remoteEntry.js","mappings":"6CAAIA,EACAC,ECDAC,EAIAC,EAIAC,EAIAC,EAIAC,EAGAC,EAIAC,EAQAC,EAOAC,EAiBAC,EAOAC,EASAC,EAQAC,EAGAC,EAWAC,EA4BAC,EACAC,EAOAC,EAQAC,E,cCzIJ,IAAIC,EAAY,CACf,6BAA8B,IACtBC,QAAQC,IAAI,CAACC,EAAoBC,EAAE,KAAMD,EAAoBC,EAAE,OAAOC,KAAK,IAAM,IAASF,EAAoB,QAGnHjB,EAAM,CAACoB,EAAQC,KAClBJ,EAAoBK,EAAID,EACxBA,EACCJ,EAAoBM,EAAET,EAAWM,GAC9BN,EAAUM,KACVL,QAAQS,UAAUL,KAAK,KACxB,MAAM,IAAIM,MAAM,WAAaL,EAAS,oCAGzCH,EAAoBK,OAAII,EACjBL,GAEJf,EAAO,CAACqB,EAAYC,KACvB,GAAKX,EAAoBY,EAAzB,CACA,IAAIC,EAAO,UACPC,EAAWd,EAAoBY,EAAEC,GACrC,GAAGC,GAAYA,IAAaJ,EAAY,MAAM,IAAIF,MAAM,mGAExD,OADAR,EAAoBY,EAAEC,GAAQH,EACvBV,EAAoBe,EAAEF,EAAMF,EALD,GASnCX,EAAoBgB,EAAEC,EAAS,CAC9BlC,IAAK,IAAM,EACXM,KAAM,IAAM,G,GC5BT6B,EAA2B,CAAC,EAGhC,SAASlB,EAAoBmB,GAE5B,IAAIC,EAAeF,EAAyBC,GAC5C,QAAqBV,IAAjBW,EACH,OAAOA,EAAaH,QAGrB,IAAId,EAASe,EAAyBC,GAAY,CACjDE,GAAIF,EACJG,QAAQ,EACRL,QAAS,CAAC,GAUX,OANAM,EAAoBJ,GAAUhB,EAAQA,EAAOc,QAASjB,GAGtDG,EAAOmB,QAAS,EAGTnB,EAAOc,OACf,CAGAjB,EAAoBwB,EAAID,EAGxBvB,EAAoByB,EAAIP,EC9BxBlB,EAAoB0B,EAAKvB,IACxB,IAAIwB,EAASxB,GAAUA,EAAOyB,WAC7B,IAAOzB,EAAiB,QACxB,IAAM,EAEP,OADAH,EAAoBgB,EAAEW,EAAQ,CAAEE,EAAGF,IAC5BA,GCLR3B,EAAoBgB,EAAI,CAACC,EAASa,KACjC,IAAI,IAAIC,KAAOD,EACX9B,EAAoBM,EAAEwB,EAAYC,KAAS/B,EAAoBM,EAAEW,EAASc,IAC5EC,OAAOC,eAAehB,EAASc,EAAK,CAAEG,YAAY,EAAMnD,IAAK+C,EAAWC,MCJ3E/B,EAAoBmC,EAAI,CAAC,EAGzBnC,EAAoBC,EAAKmC,GACjBtC,QAAQC,IAAIiC,OAAOK,KAAKrC,EAAoBmC,GAAGG,OAAO,CAACC,EAAUR,KACvE/B,EAAoBmC,EAAEJ,GAAKK,EAASG,GAC7BA,GACL,KCNJvC,EAAoBwC,EAAKJ,GAEZA,EAAU,IAAM,CAAC,IAAM,uBAAuB,IAAM,uBAAuB,IAAM,wBAAwBA,GAAW,MCFjIpC,EAAoByC,SAAYL,MCDhCpC,EAAoB0C,EAAI,WACvB,GAA0B,iBAAfC,WAAyB,OAAOA,WAC3C,IACC,OAAOC,MAAQ,IAAIC,SAAS,cAAb,EAChB,CAAE,MAAO5C,GACR,GAAsB,iBAAX6C,OAAqB,OAAOA,MACxC,CACA,CAPuB,GCAxB9C,EAAoBM,EAAI,CAACyC,EAAKC,IAAUhB,OAAOiB,UAAUC,eAAeC,KAAKJ,EAAKC,GVA9ExE,EAAa,CAAC,EACdC,EAAoB,qBAExBuB,EAAoBoD,EAAI,CAACC,EAAKC,EAAMvB,EAAKK,KACxC,GAAG5D,EAAW6E,GAAQ7E,EAAW6E,GAAKE,KAAKD,OAA3C,CACA,IAAIE,EAAQC,EACZ,QAAWhD,IAARsB,EAEF,IADA,IAAI2B,EAAUC,SAASC,qBAAqB,UACpCC,EAAI,EAAGA,EAAIH,EAAQI,OAAQD,IAAK,CACvC,IAAIE,EAAIL,EAAQG,GAChB,GAAGE,EAAEC,aAAa,QAAUX,GAAOU,EAAEC,aAAa,iBAAmBvF,EAAoBsD,EAAK,CAAEyB,EAASO,EAAG,KAAO,CACpH,CAEGP,IACHC,GAAa,GACbD,EAASG,SAASM,cAAc,WAEzBC,QAAU,QACblE,EAAoBmE,IACvBX,EAAOY,aAAa,QAASpE,EAAoBmE,IAElDX,EAAOY,aAAa,eAAgB3F,EAAoBsD,GAExDyB,EAAOa,IAAMhB,GAEd7E,EAAW6E,GAAO,CAACC,GACnB,IAAIgB,EAAmB,CAACC,EAAMC,KAE7BhB,EAAOiB,QAAUjB,EAAOkB,OAAS,KACjCC,aAAaC,GACb,IAAIC,EAAUrG,EAAW6E,GAIzB,UAHO7E,EAAW6E,GAClBG,EAAOsB,YAActB,EAAOsB,WAAWC,YAAYvB,GACnDqB,GAAWA,EAAQG,QAASC,GAAQA,EAAGT,IACpCD,EAAM,OAAOA,EAAKC,IAElBI,EAAUM,WAAWZ,EAAiBa,KAAK,UAAM1E,EAAW,CAAE2E,KAAM,UAAWC,OAAQ7B,IAAW,MACtGA,EAAOiB,QAAUH,EAAiBa,KAAK,KAAM3B,EAAOiB,SACpDjB,EAAOkB,OAASJ,EAAiBa,KAAK,KAAM3B,EAAOkB,QACnDjB,GAAcE,SAAS2B,KAAKC,YAAY/B,EAnCkB,GWH3DxD,EAAoBwF,EAAKvE,IACH,oBAAXwE,QAA0BA,OAAOC,aAC1C1D,OAAOC,eAAehB,EAASwE,OAAOC,YAAa,CAAEC,MAAO,WAE7D3D,OAAOC,eAAehB,EAAS,aAAc,CAAE0E,OAAO,KCLvD3F,EAAoB4F,IAAOzF,IAC1BA,EAAO0F,MAAQ,GACV1F,EAAO2F,WAAU3F,EAAO2F,SAAW,IACjC3F,G,MCHRH,EAAoBY,EAAI,CAAC,EACzB,IAAImF,EAAe,CAAC,EAChBC,EAAa,CAAC,EAClBhG,EAAoBe,EAAI,CAACF,EAAMF,KAC1BA,IAAWA,EAAY,IAE3B,IAAIsF,EAAYD,EAAWnF,GAE3B,GADIoF,IAAWA,EAAYD,EAAWnF,GAAQ,CAAC,KAC5CF,EAAUuF,QAAQD,IAAc,GAAnC,CAGA,GAFAtF,EAAU4C,KAAK0C,GAEZF,EAAalF,GAAO,OAAOkF,EAAalF,GAEvCb,EAAoBM,EAAEN,EAAoBY,EAAGC,KAAOb,EAAoBY,EAAEC,GAAQ,CAAC,GAEvF,IAAIsF,EAAQnG,EAAoBY,EAAEC,GAI9BuF,EAAa,oBAiBb7D,EAAW,GAOf,MALM,YADC1B,GAjBQ,EAACA,EAAMwF,EAASC,EAASC,KACvC,IAAIC,EAAWL,EAAMtF,GAAQsF,EAAMtF,IAAS,CAAC,EACzC4F,EAAgBD,EAASH,KACzBI,IAAmBA,EAAcnF,SAAW,IAAWmF,EAAcF,MAAQA,EAAQH,EAAaK,EAAcC,SAAQF,EAASH,GAAW,CAAEtH,IAgBpH,IAAOiB,EAAoBC,EAAE,KAAKC,KAAK,IAAM,IAAQF,EAAoB,OAhByD0G,KAAMN,EAAYG,OAAO,KAgBxLI,CAAS,QAAS,WAKbZ,EAAalF,GADhB0B,EAASuB,OACehE,QAAQC,IAAIwC,GAAUrC,KAAK,IAAO6F,EAAalF,GAAQ,GADlC,CAnCL,E,WCR7C,IAAI+F,EACA5G,EAAoB0C,EAAEmE,gBAAeD,EAAY5G,EAAoB0C,EAAEoE,SAAW,IACtF,IAAInD,EAAW3D,EAAoB0C,EAAEiB,SACrC,IAAKiD,GAAajD,IACbA,EAASoD,eAAkE,WAAjDpD,EAASoD,cAAcC,QAAQC,gBAC5DL,EAAYjD,EAASoD,cAAc1C,MAC/BuC,GAAW,CACf,IAAIlD,EAAUC,EAASC,qBAAqB,UAC5C,GAAGF,EAAQI,OAEV,IADA,IAAID,EAAIH,EAAQI,OAAS,EAClBD,GAAK,KAAO+C,IAAc,aAAaM,KAAKN,KAAaA,EAAYlD,EAAQG,KAAKQ,GAE3F,CAID,IAAKuC,EAAW,MAAM,IAAIpG,MAAM,yDAChCoG,EAAYA,EAAUO,QAAQ,SAAU,IAAIA,QAAQ,OAAQ,IAAIA,QAAQ,QAAS,IAAIA,QAAQ,YAAa,KAC1GnH,EAAoBoH,EAAIR,C,KblBpBlI,EAAgB2I,IAEnB,IAAID,EAAEA,GAAWA,EAAEE,MAAM,KAAKC,IAAIH,IAAWA,GAAGA,GAAGA,EAAEA,GAAK1F,EAAE,sCAAsC8F,KAAKH,GAAK7B,EAAE9D,EAAE,GAAG0F,EAAE1F,EAAE,IAAI,GAAG,OAAOA,EAAE,KAAK8D,EAAE1B,SAAS0B,EAAEjC,KAAKkE,MAAMjC,EAAE4B,EAAE1F,EAAE,MAAMA,EAAE,KAAK8D,EAAEjC,KAAK,IAAIiC,EAAEjC,KAAKkE,MAAMjC,EAAE4B,EAAE1F,EAAE,MAAM8D,GAExN7G,EAAY,CAACkD,EAAG6F,KAEnB7F,EAAEnD,EAAamD,GAAG6F,EAAEhJ,EAAagJ,GAAG,IAAI,IAAIlC,EAAE,IAAI,CAAC,GAAGA,GAAG3D,EAAEiC,OAAO,OAAO0B,EAAEkC,EAAE5D,QAAQ,aAAa4D,EAAElC,IAAI,GAAG,IAAIvF,EAAE4B,EAAE2D,GAAG9D,UAAUzB,GAAG,GAAG,GAAGuF,GAAGkC,EAAE5D,OAAO,MAAM,KAAKpC,EAAE,IAAIiG,EAAED,EAAElC,GAAGrD,UAAUwF,GAAG,GAAG,GAAGjG,GAAGS,EAAE,MAAM,KAAKT,GAAG,KAAKS,GAAI,KAAKA,GAAG,KAAKT,EAAG,GAAG,KAAKA,GAAG,KAAKA,GAAGzB,GAAG0H,EAAE,OAAO1H,EAAE0H,EAAEnC,GAAG,GAE/Q5G,EAAiBgJ,IAEpB,IAAIpC,EAAEoC,EAAM,GAAGlG,EAAE,GAAG,GAAG,IAAIkG,EAAM9D,OAAO,MAAM,IAAI,GAAG0B,EAAE,GAAG,CAAC9D,GAAG,GAAG8D,EAAE,MAAM,GAAGA,EAAE,IAAI,GAAGA,EAAE,IAAI,GAAGA,EAAE,IAAIA,EAAE,EAAE,IAAI,KAAK,IAAI,IAAIvF,EAAE,EAAE4B,EAAE,EAAEA,EAAE+F,EAAM9D,OAAOjC,IAAK5B,IAAIyB,GAAG,aAAaiG,EAAEC,EAAM/F,KAAK,GAAG,KAAK5B,EAAE,EAAE,IAAI,KAAKA,EAAE,EAAE0H,GAAG,OAAOjG,CAAC,CAAC,IAAIgB,EAAE,GAAG,IAAIb,EAAE,EAAEA,EAAE+F,EAAM9D,OAAOjC,IAAI,CAAC,IAAI8F,EAAEC,EAAM/F,GAAGa,EAAEa,KAAK,IAAIoE,EAAE,OAAOrH,IAAI,IAAI,IAAIqH,EAAE,IAAIrH,IAAI,OAAOA,IAAI,IAAI,IAAIqH,EAAEjF,EAAEmF,MAAM,IAAInF,EAAEmF,MAAMjJ,EAAc+I,GAAG,CAAC,OAAOrH,IAAI,SAASA,IAAI,OAAOoC,EAAEmF,MAAMV,QAAQ,aAAa,KAAK,GAElbtI,EAAU,CAAC+I,EAAOvB,KAErB,GAAG,KAAKuB,EAAM,CAACvB,EAAQ3H,EAAa2H,GAAS,IAAIpG,EAAE2H,EAAM,GAAGpC,EAAEvF,EAAE,EAAEuF,IAAIvF,GAAGA,EAAE,GAAG,IAAI,IAAIyB,EAAE,EAAEmC,EAAE,EAAEhC,GAAE,GAAIgC,IAAInC,IAAI,CAAC,IAAIS,EAAE4B,EAAErB,EAAEmB,EAAE+D,EAAM9D,eAAe8D,EAAM/D,IAAI,GAAG,GAAG,GAAGnC,GAAG2E,EAAQvC,QAAQ,MAAMC,UAAU5B,EAAEkE,EAAQ3E,KAAK,IAAI,OAAOG,IAAI,KAAKa,EAAEmB,EAAE5D,IAAIuF,EAAE,IAAI9C,GAAG8C,GAAG,GAAG,KAAKzB,GAAG,IAAIlC,GAAG,KAAKa,EAAE,OAAM,OAAQ,GAAGb,EAAE,GAAGa,GAAGqB,EAAE,GAAGF,GAAG5D,GAAG,GAAGkC,GAAGyF,EAAM/D,GAAG,OAAM,MAAO,CAAC,GAAG2B,EAAErD,EAAEyF,EAAM/D,GAAG1B,EAAEyF,EAAM/D,GAAG,OAAM,EAAG1B,GAAGyF,EAAM/D,KAAKhC,GAAE,EAAG,MAAM,GAAG,KAAKa,GAAG,KAAKA,EAAE,CAAC,GAAG8C,GAAG3B,GAAG5D,EAAE,OAAM,EAAG4B,GAAE,EAAGgC,GAAG,KAAK,CAAC,GAAGA,GAAG5D,GAAG8D,EAAErB,GAAG8C,EAAE,OAAM,EAAG3D,GAAE,CAAE,KAAK,KAAKa,GAAG,KAAKA,IAAIb,GAAE,EAAGgC,IAAI,CAAC,CAAC,IAAI8D,EAAE,GAAGrH,EAAEqH,EAAEE,IAAI1C,KAAKwC,GAAG,IAAIjG,EAAE,EAAEA,EAAEkG,EAAM9D,OAAOpC,IAAI,CAAC,IAAIc,EAAEoF,EAAMlG,GAAGiG,EAAEpE,KAAK,GAAGf,EAAElC,IAAIA,IAAI,GAAGkC,EAAElC,IAAIA,IAAIkC,EAAE3D,EAAQ2D,EAAE6D,IAAU/F,IAAI,CAAC,QAAQA,KAE1oBxB,EAAS,CAACqH,EAAOpE,IACboE,GAASnG,EAAoBM,EAAE6F,EAAOpE,GAE1ChD,EAAO+I,IACVA,EAAMxG,OAAS,EACRwG,EAAM/I,OAEVC,EAAawH,GACTxE,OAAOK,KAAKmE,GAAUlE,OAAO,CAACyF,EAAU1B,KACzCG,EAASH,GAASE,QACrBwB,EAAS1B,GAAWG,EAASH,IAEvB0B,GACN,CAAC,GAED9I,EAAoB,CAACkH,EAAOpE,EAAKwE,KACpC,IAAIC,EAAWD,EAAQvH,EAAUmH,EAAMpE,IAAQoE,EAAMpE,GAIrD,OAHIA,EAAMC,OAAOK,KAAKmE,GAAUlE,OAAO,CAACT,EAAG6F,KAClC7F,GAAKlD,EAAUkD,EAAG6F,GAAKA,EAAI7F,EACjC,KACW2E,EAASzE,IAEpB7C,EAAwB,CAACiH,EAAOpE,EAAKiG,EAAiBzB,KACzD,IAAIC,EAAWD,EAAQvH,EAAUmH,EAAMpE,IAAQoE,EAAMpE,GAKrD,OAJIA,EAAMC,OAAOK,KAAKmE,GAAUlE,OAAO,CAACT,EAAG6F,KACrC7I,EAAQmJ,EAAiBN,IACtB7F,IAAKlD,EAAUkD,EAAG6F,GADe7F,EACV6F,EAC7B,KACWlB,EAASzE,IAWpB5C,EAA2B,CAACgH,EAAO8B,EAAWlG,EAAKiG,EAAiBzB,KACvE,IAAIC,EAAWL,EAAMpE,GACrB,MAAO,0BAA4BnD,EAAcoJ,GAAmB,KAAOzB,EAAQ,yBAA2B,IAAM,qBAAuBxE,EAAM,0BAA4BkG,EAAtK,0BACmBjG,OAAOK,KAAKmE,GAAUe,IAAKxF,GAC7CA,EAAM,SAAWyE,EAASzE,GAAK2E,MACpCwB,KAAK,OAEL9I,EAAQ+I,IACX,MAAM,IAAI3H,MAAM2H,IAgBb7I,EAAc,CAAC2I,EAAWlG,EAAKqG,IAC3BA,EAAWA,IAfE,EAACH,EAAWlG,IACzB3C,EAAK,iBAAmB2C,EAAM,kCAAoCkG,GAc1CI,CAAeJ,EAAWlG,GAEtDxC,GAXAF,EAAQ4F,GAAO,SAAUgD,EAAWlG,EAAKwE,EAAO9E,EAAGT,GACtD,IAAIsH,EAAUtI,EAAoBe,EAAEkH,GACpC,OAAIK,GAAWA,EAAQpI,OAASqG,EACxB+B,EAAQpI,KAAK+E,EAAGE,KAAKF,EAAIgD,EAAWjI,EAAoBY,EAAEqH,GAAYlG,GAAK,EAAON,EAAGT,IAEtFiE,EAAGgD,EAAWjI,EAAoBY,EAAEqH,GAAYlG,EAAKwE,EAAO9E,EAAGT,EACtE,GAK6B,CAACiH,EAAW9B,EAAOpE,EAAKwE,EAAO6B,IACvDtJ,EAAOqH,EAAOpE,GACZhD,EAAIE,EAAkBkH,EAAOpE,EAAKwE,IADTjH,EAAY2I,EAAWlG,EAAKqG,IAUzD5I,EAAkCH,EAAK,CAAC4I,EAAW9B,EAAOpE,EAAKwE,EAAOyB,EAAiBI,KAC1F,IAAKtJ,EAAOqH,EAAOpE,GAAM,OAAOzC,EAAY2I,EAAWlG,EAAKqG,GAC5D,IAAIG,EAAoBrJ,EAAsBiH,EAAOpE,EAAKiG,EAAiBzB,GAC3E,OAAIgC,EAA0BxJ,EAAIwJ,GAC9BH,EAAiBA,SACrBhJ,EAAKD,EAAyBgH,EAAO8B,EAAWlG,EAAKiG,EAAiBzB,MAuBnE9G,EAAmB,CAAC,EACpBC,EAAyB,CAC5B,KAAM,IAAOF,EAAkB,UAAW,SAAS,EAAO,CAAC,EAAE,GAAG,GAAG,GAAI,IAAOQ,EAAoBC,EAAE,KAAKC,KAAK,IAAM,IAAQF,EAAoB,QAChJ,KAAM,IAAOT,EAAK,UAAW,SAAS,EAAO,IAAOS,EAAoBC,EAAE,KAAKC,KAAK,IAAM,IAAQF,EAAoB,QACtH,KAAM,IAAOR,EAAkB,UAAW,SAAS,EAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,GAAG,GAAI,IAAOQ,EAAoBC,EAAE,KAAKC,KAAK,IAAM,IAAQF,EAAoB,QAC5J,KAAM,IAAOR,EAAkB,UAAW,SAAS,EAAO,CAAC,EAAE,EAAE,GAAG,GAAI,IAAOQ,EAAoBC,EAAE,KAAKC,KAAK,IAAM,IAAQF,EAAoB,SAG5IL,EAAe,CAClB,IAAO,CACN,KACA,KACA,KACA,OAGEC,EAAwB,CAAC,EAC7BI,EAAoBmC,EAAEqG,SAAW,CAACpG,EAASG,KACvCvC,EAAoBM,EAAEX,EAAcyC,IACtCzC,EAAayC,GAAS4C,QAAS3D,IAC9B,GAAGrB,EAAoBM,EAAEb,EAAkB4B,GAAK,OAAOkB,EAASgB,KAAK9D,EAAiB4B,IACtF,IAAIzB,EAAsByB,GAAK,CAC/B,IAAIoH,EAAanC,IAChB7G,EAAiB4B,GAAM,EACvBrB,EAAoBwB,EAAEH,GAAOlB,WACrBH,EAAoByB,EAAEJ,GAC7BlB,EAAOc,QAAUqF,MAGnB1G,EAAsByB,IAAM,EAC5B,IAAIqH,EAAWC,WACPlJ,EAAiB4B,GACxBrB,EAAoBwB,EAAEH,GAAOlB,IAE5B,aADOH,EAAoByB,EAAEJ,GACvBsH,IAGR,IACC,IAAIL,EAAU5I,EAAuB2B,KAClCiH,EAAQpI,KACVqC,EAASgB,KAAK9D,EAAiB4B,GAAMiH,EAAQpI,KAAKuI,GAAkB,MAAEC,IAChED,EAAUH,EAClB,CAAE,MAAMrI,GAAKyI,EAAQzI,EAAI,CACzB,K,Mc/JH,IAAI2I,EAAkB,CACrB,IAAK,GAGN5I,EAAoBmC,EAAE0G,EAAI,CAACzG,EAASG,KAElC,IAAIuG,EAAqB9I,EAAoBM,EAAEsI,EAAiBxG,GAAWwG,EAAgBxG,QAAW3B,EACtG,GAA0B,IAAvBqI,EAGF,GAAGA,EACFvG,EAASgB,KAAKuF,EAAmB,QAC3B,CAGL,IAAIR,EAAU,IAAIxI,QAAQ,CAACS,EAASwI,IAAYD,EAAqBF,EAAgBxG,GAAW,CAAC7B,EAASwI,IAC1GxG,EAASgB,KAAKuF,EAAmB,GAAKR,GAGtC,IAAIjF,EAAMrD,EAAoBoH,EAAIpH,EAAoBwC,EAAEJ,GAEpDuG,EAAQ,IAAInI,MAgBhBR,EAAoBoD,EAAEC,EAfFmB,IACnB,GAAGxE,EAAoBM,EAAEsI,EAAiBxG,KAEf,KAD1B0G,EAAqBF,EAAgBxG,MACRwG,EAAgBxG,QAAW3B,GACrDqI,GAAoB,CACtB,IAAIE,EAAYxE,IAAyB,SAAfA,EAAMY,KAAkB,UAAYZ,EAAMY,MAChE6D,EAAUzE,GAASA,EAAMa,QAAUb,EAAMa,OAAOhB,IACpDsE,EAAMO,QAAU,iBAAmB9G,EAAU,cAAgB4G,EAAY,KAAOC,EAAU,IAC1FN,EAAM9H,KAAO,iBACb8H,EAAMvD,KAAO4D,EACbL,EAAMQ,QAAUF,EAChBH,EAAmB,GAAGH,EACvB,GAGuC,SAAWvG,EAASA,EAE/D,GAeH,IAAIgH,EAAuB,CAACC,EAA4BC,KACvD,IAGInI,EAAUiB,GAHTmH,EAAUC,EAAaC,GAAWH,EAGhBzF,EAAI,EAC3B,GAAG0F,EAASG,KAAMrI,GAAgC,IAAxBuH,EAAgBvH,IAAa,CACtD,IAAIF,KAAYqI,EACZxJ,EAAoBM,EAAEkJ,EAAarI,KACrCnB,EAAoBwB,EAAEL,GAAYqI,EAAYrI,IAG7CsI,GAAsBA,EAAQzJ,EAClC,CAEA,IADGqJ,GAA4BA,EAA2BC,GACrDzF,EAAI0F,EAASzF,OAAQD,IACzBzB,EAAUmH,EAAS1F,GAChB7D,EAAoBM,EAAEsI,EAAiBxG,IAAYwG,EAAgBxG,IACrEwG,EAAgBxG,GAAS,KAE1BwG,EAAgBxG,GAAW,GAKzBuH,EAAqBC,KAAoC,8BAAIA,KAAoC,+BAAK,GAC1GD,EAAmB3E,QAAQoE,EAAqBjE,KAAK,KAAM,IAC3DwE,EAAmBpG,KAAO6F,EAAqBjE,KAAK,KAAMwE,EAAmBpG,KAAK4B,KAAKwE,G,KClFvF,IAAIE,EAAsB7J,EAAoB,K","sources":["webpack://signalk-edge-link/webpack/runtime/load script","webpack://signalk-edge-link/webpack/runtime/consumes","webpack://signalk-edge-link/webpack/container-entry","webpack://signalk-edge-link/webpack/bootstrap","webpack://signalk-edge-link/webpack/runtime/compat get default export","webpack://signalk-edge-link/webpack/runtime/define property getters","webpack://signalk-edge-link/webpack/runtime/ensure chunk","webpack://signalk-edge-link/webpack/runtime/get javascript chunk filename","webpack://signalk-edge-link/webpack/runtime/get mini-css chunk filename","webpack://signalk-edge-link/webpack/runtime/global","webpack://signalk-edge-link/webpack/runtime/hasOwnProperty shorthand","webpack://signalk-edge-link/webpack/runtime/make namespace object","webpack://signalk-edge-link/webpack/runtime/node module decorator","webpack://signalk-edge-link/webpack/runtime/sharing","webpack://signalk-edge-link/webpack/runtime/publicPath","webpack://signalk-edge-link/webpack/runtime/jsonp chunk loading","webpack://signalk-edge-link/webpack/startup"],"sourcesContent":["var inProgress = {};\nvar dataWebpackPrefix = \"signalk-edge-link:\";\n// loadScript function to load a script via script tag\n__webpack_require__.l = (url, done, key, chunkId) => {\n\tif(inProgress[url]) { inProgress[url].push(done); return; }\n\tvar script, needAttach;\n\tif(key !== undefined) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tfor(var i = 0; i < scripts.length; i++) {\n\t\t\tvar s = scripts[i];\n\t\t\tif(s.getAttribute(\"src\") == url || s.getAttribute(\"data-webpack\") == dataWebpackPrefix + key) { script = s; break; }\n\t\t}\n\t}\n\tif(!script) {\n\t\tneedAttach = true;\n\t\tscript = document.createElement('script');\n\n\t\tscript.charset = 'utf-8';\n\t\tif (__webpack_require__.nc) {\n\t\t\tscript.setAttribute(\"nonce\", __webpack_require__.nc);\n\t\t}\n\t\tscript.setAttribute(\"data-webpack\", dataWebpackPrefix + key);\n\n\t\tscript.src = url;\n\t}\n\tinProgress[url] = [done];\n\tvar onScriptComplete = (prev, event) => {\n\t\t// avoid mem leaks in IE.\n\t\tscript.onerror = script.onload = null;\n\t\tclearTimeout(timeout);\n\t\tvar doneFns = inProgress[url];\n\t\tdelete inProgress[url];\n\t\tscript.parentNode && script.parentNode.removeChild(script);\n\t\tdoneFns && doneFns.forEach((fn) => (fn(event)));\n\t\tif(prev) return prev(event);\n\t}\n\tvar timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000);\n\tscript.onerror = onScriptComplete.bind(null, script.onerror);\n\tscript.onload = onScriptComplete.bind(null, script.onload);\n\tneedAttach && document.head.appendChild(script);\n};","var parseVersion = (str) => {\n\t// see webpack/lib/util/semver.js for original code\n\tvar p=p=>{return p.split(\".\").map(p=>{return+p==p?+p:p})},n=/^([^-+]+)?(?:-([^+]+))?(?:\\+(.+))?$/.exec(str),r=n[1]?p(n[1]):[];return n[2]&&(r.length++,r.push.apply(r,p(n[2]))),n[3]&&(r.push([]),r.push.apply(r,p(n[3]))),r;\n}\nvar versionLt = (a, b) => {\n\t// see webpack/lib/util/semver.js for original code\n\ta=parseVersion(a),b=parseVersion(b);for(var r=0;;){if(r>=a.length)return r<b.length&&\"u\"!=(typeof b[r])[0];var e=a[r],n=(typeof e)[0];if(r>=b.length)return\"u\"==n;var t=b[r],f=(typeof t)[0];if(n!=f)return\"o\"==n&&\"n\"==f||(\"s\"==f||\"u\"==n);if(\"o\"!=n&&\"u\"!=n&&e!=t)return e<t;r++}\n}\nvar rangeToString = (range) => {\n\t// see webpack/lib/util/semver.js for original code\n\tvar r=range[0],n=\"\";if(1===range.length)return\"*\";if(r+.5){n+=0==r?\">=\":-1==r?\"<\":1==r?\"^\":2==r?\"~\":r>0?\"=\":\"!=\";for(var e=1,a=1;a<range.length;a++){e--,n+=\"u\"==(typeof(t=range[a]))[0]?\"-\":(e>0?\".\":\"\")+(e=2,t)}return n}var g=[];for(a=1;a<range.length;a++){var t=range[a];g.push(0===t?\"not(\"+o()+\")\":1===t?\"(\"+o()+\" || \"+o()+\")\":2===t?g.pop()+\" \"+g.pop():rangeToString(t))}return o();function o(){return g.pop().replace(/^\\((.+)\\)$/,\"$1\")}\n}\nvar satisfy = (range, version) => {\n\t// see webpack/lib/util/semver.js for original code\n\tif(0 in range){version=parseVersion(version);var e=range[0],r=e<0;r&&(e=-e-1);for(var n=0,i=1,a=!0;;i++,n++){var f,s,g=i<range.length?(typeof range[i])[0]:\"\";if(n>=version.length||\"o\"==(s=(typeof(f=version[n]))[0]))return!a||(\"u\"==g?i>e&&!r:\"\"==g!=r);if(\"u\"==s){if(!a||\"u\"!=g)return!1}else if(a)if(g==s)if(i<=e){if(f!=range[i])return!1}else{if(r?f>range[i]:f<range[i])return!1;f!=range[i]&&(a=!1)}else if(\"s\"!=g&&\"n\"!=g){if(r||i<=e)return!1;a=!1,i--}else{if(i<=e||s<g!=r)return!1;a=!1}else\"s\"!=g&&\"n\"!=g&&(a=!1,i--)}}var t=[],o=t.pop.bind(t);for(n=1;n<range.length;n++){var u=range[n];t.push(1==u?o()|o():2==u?o()&o():u?satisfy(u,version):!o())}return!!o();\n}\nvar exists = (scope, key) => {\n\treturn scope && __webpack_require__.o(scope, key);\n}\nvar get = (entry) => {\n\tentry.loaded = 1;\n\treturn entry.get()\n};\nvar eagerOnly = (versions) => {\n\treturn Object.keys(versions).reduce((filtered, version) => {\n\t\t\tif (versions[version].eager) {\n\t\t\t\tfiltered[version] = versions[version];\n\t\t\t}\n\t\t\treturn filtered;\n\t}, {});\n};\nvar findLatestVersion = (scope, key, eager) => {\n\tvar versions = eager ? eagerOnly(scope[key]) : scope[key];\n\tvar key = Object.keys(versions).reduce((a, b) => {\n\t\treturn !a || versionLt(a, b) ? b : a;\n\t}, 0);\n\treturn key && versions[key];\n};\nvar findSatisfyingVersion = (scope, key, requiredVersion, eager) => {\n\tvar versions = eager ? eagerOnly(scope[key]) : scope[key];\n\tvar key = Object.keys(versions).reduce((a, b) => {\n\t\tif (!satisfy(requiredVersion, b)) return a;\n\t\treturn !a || versionLt(a, b) ? b : a;\n\t}, 0);\n\treturn key && versions[key]\n};\nvar findSingletonVersionKey = (scope, key, eager) => {\n\tvar versions = eager ? eagerOnly(scope[key]) : scope[key];\n\treturn Object.keys(versions).reduce((a, b) => {\n\t\treturn !a || (!versions[a].loaded && versionLt(a, b)) ? b : a;\n\t}, 0);\n};\nvar getInvalidSingletonVersionMessage = (scope, key, version, requiredVersion) => {\n\treturn \"Unsatisfied version \" + version + \" from \" + (version && scope[key][version].from) + \" of shared singleton module \" + key + \" (required \" + rangeToString(requiredVersion) + \")\"\n};\nvar getInvalidVersionMessage = (scope, scopeName, key, requiredVersion, eager) => {\n\tvar versions = scope[key];\n\treturn \"No satisfying version (\" + rangeToString(requiredVersion) + \")\" + (eager ? \" for eager consumption\" : \"\") + \" of shared module \" + key + \" found in shared scope \" + scopeName + \".\\n\" +\n\t\t\"Available versions: \" + Object.keys(versions).map((key) => {\n\t\treturn key + \" from \" + versions[key].from;\n\t}).join(\", \");\n};\nvar fail = (msg) => {\n\tthrow new Error(msg);\n}\nvar failAsNotExist = (scopeName, key) => {\n\treturn fail(\"Shared module \" + key + \" doesn't exist in shared scope \" + scopeName);\n}\nvar warn = /*#__PURE__*/ (msg) => {\n\tif (typeof console !== \"undefined\" && console.warn) console.warn(msg);\n};\nvar init = (fn) => (function(scopeName, key, eager, c, d) {\n\tvar promise = __webpack_require__.I(scopeName);\n\tif (promise && promise.then && !eager) {\n\t\treturn promise.then(fn.bind(fn, scopeName, __webpack_require__.S[scopeName], key, false, c, d));\n\t}\n\treturn fn(scopeName, __webpack_require__.S[scopeName], key, eager, c, d);\n});\n\nvar useFallback = (scopeName, key, fallback) => {\n\treturn fallback ? fallback() : failAsNotExist(scopeName, key);\n}\nvar load = /*#__PURE__*/ init((scopeName, scope, key, eager, fallback) => {\n\tif (!exists(scope, key)) return useFallback(scopeName, key, fallback);\n\treturn get(findLatestVersion(scope, key, eager));\n});\nvar loadVersion = /*#__PURE__*/ init((scopeName, scope, key, eager, requiredVersion, fallback) => {\n\tif (!exists(scope, key)) return useFallback(scopeName, key, fallback);\n\tvar satisfyingVersion = findSatisfyingVersion(scope, key, requiredVersion, eager);\n\tif (satisfyingVersion) return get(satisfyingVersion);\n\twarn(getInvalidVersionMessage(scope, scopeName, key, requiredVersion, eager))\n\treturn get(findLatestVersion(scope, key, eager));\n});\nvar loadStrictVersion = /*#__PURE__*/ init((scopeName, scope, key, eager, requiredVersion, fallback) => {\n\tif (!exists(scope, key)) return useFallback(scopeName, key, fallback);\n\tvar satisfyingVersion = findSatisfyingVersion(scope, key, requiredVersion, eager);\n\tif (satisfyingVersion) return get(satisfyingVersion);\n\tif (fallback) return fallback();\n\tfail(getInvalidVersionMessage(scope, scopeName, key, requiredVersion, eager));\n});\nvar loadSingleton = /*#__PURE__*/ init((scopeName, scope, key, eager, fallback) => {\n\tif (!exists(scope, key)) return useFallback(scopeName, key, fallback);\n\tvar version = findSingletonVersionKey(scope, key, eager);\n\treturn get(scope[key][version]);\n});\nvar loadSingletonVersion = /*#__PURE__*/ init((scopeName, scope, key, eager, requiredVersion, fallback) => {\n\tif (!exists(scope, key)) return useFallback(scopeName, key, fallback);\n\tvar version = findSingletonVersionKey(scope, key, eager);\n\tif (!satisfy(requiredVersion, version)) {\n\t\twarn(getInvalidSingletonVersionMessage(scope, key, version, requiredVersion));\n\t}\n\treturn get(scope[key][version]);\n});\nvar loadStrictSingletonVersion = /*#__PURE__*/ init((scopeName, scope, key, eager, requiredVersion, fallback) => {\n\tif (!exists(scope, key)) return useFallback(scopeName, key, fallback);\n\tvar version = findSingletonVersionKey(scope, key, eager);\n\tif (!satisfy(requiredVersion, version)) {\n\t\tfail(getInvalidSingletonVersionMessage(scope, key, version, requiredVersion));\n\t}\n\treturn get(scope[key][version]);\n});\nvar installedModules = {};\nvar moduleToHandlerMapping = {\n\t4147: () => (loadStrictVersion(\"default\", \"react\", false, [1,16,13,1], () => (__webpack_require__.e(540).then(() => (() => (__webpack_require__(6540))))))),\n\t4167: () => (load(\"default\", \"react\", false, () => (__webpack_require__.e(540).then(() => (() => (__webpack_require__(6540))))))),\n\t7920: () => (loadStrictVersion(\"default\", \"react\", false, [,[0,17],[1,16,14,0],1], () => (__webpack_require__.e(540).then(() => (() => (__webpack_require__(6540))))))),\n\t8271: () => (loadStrictVersion(\"default\", \"react\", false, [0,0,14,0], () => (__webpack_require__.e(540).then(() => (() => (__webpack_require__(6540)))))))\n};\n// no consumes in initial chunks\nvar chunkMapping = {\n\t\"982\": [\n\t\t4147,\n\t\t4167,\n\t\t7920,\n\t\t8271\n\t]\n};\nvar startedInstallModules = {};\n__webpack_require__.f.consumes = (chunkId, promises) => {\n\tif(__webpack_require__.o(chunkMapping, chunkId)) {\n\t\tchunkMapping[chunkId].forEach((id) => {\n\t\t\tif(__webpack_require__.o(installedModules, id)) return promises.push(installedModules[id]);\n\t\t\tif(!startedInstallModules[id]) {\n\t\t\tvar onFactory = (factory) => {\n\t\t\t\tinstalledModules[id] = 0;\n\t\t\t\t__webpack_require__.m[id] = (module) => {\n\t\t\t\t\tdelete __webpack_require__.c[id];\n\t\t\t\t\tmodule.exports = factory();\n\t\t\t\t}\n\t\t\t};\n\t\t\tstartedInstallModules[id] = true;\n\t\t\tvar onError = (error) => {\n\t\t\t\tdelete installedModules[id];\n\t\t\t\t__webpack_require__.m[id] = (module) => {\n\t\t\t\t\tdelete __webpack_require__.c[id];\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t};\n\t\t\ttry {\n\t\t\t\tvar promise = moduleToHandlerMapping[id]();\n\t\t\t\tif(promise.then) {\n\t\t\t\t\tpromises.push(installedModules[id] = promise.then(onFactory)['catch'](onError));\n\t\t\t\t} else onFactory(promise);\n\t\t\t} catch(e) { onError(e); }\n\t\t\t}\n\t\t});\n\t}\n}","var moduleMap = {\n\t\"./PluginConfigurationPanel\": () => {\n\t\treturn Promise.all([__webpack_require__.e(277), __webpack_require__.e(982)]).then(() => (() => ((__webpack_require__(4353)))));\n\t}\n};\nvar get = (module, getScope) => {\n\t__webpack_require__.R = getScope;\n\tgetScope = (\n\t\t__webpack_require__.o(moduleMap, module)\n\t\t\t? moduleMap[module]()\n\t\t\t: Promise.resolve().then(() => {\n\t\t\t\tthrow new Error('Module \"' + module + '\" does not exist in container.');\n\t\t\t})\n\t);\n\t__webpack_require__.R = undefined;\n\treturn getScope;\n};\nvar init = (shareScope, initScope) => {\n\tif (!__webpack_require__.S) return;\n\tvar name = \"default\"\n\tvar oldScope = __webpack_require__.S[name];\n\tif(oldScope && oldScope !== shareScope) throw new Error(\"Container initialization failed as it has already been initialized with a different share scope\");\n\t__webpack_require__.S[name] = shareScope;\n\treturn __webpack_require__.I(name, initScope);\n};\n\n// This exports getters to disallow modifications\n__webpack_require__.d(exports, {\n\tget: () => (get),\n\tinit: () => (init)\n});","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\tid: moduleId,\n\t\tloaded: false,\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Flag the module as loaded\n\tmodule.loaded = true;\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n// expose the module cache\n__webpack_require__.c = __webpack_module_cache__;\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.f = {};\n// This file contains only the entry chunk.\n// The chunk loading function for additional chunks\n__webpack_require__.e = (chunkId) => {\n\treturn Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {\n\t\t__webpack_require__.f[key](chunkId, promises);\n\t\treturn promises;\n\t}, []));\n};","// This function allow to reference async chunks\n__webpack_require__.u = (chunkId) => {\n\t// return url for filenames based on template\n\treturn \"\" + chunkId + \".\" + {\"277\":\"99e19dcb5b778c964ace\",\"540\":\"70a0bc6aadb412703390\",\"982\":\"b207a377ed6542e2fb4a\"}[chunkId] + \".js\";\n};","// This function allow to reference async chunks\n__webpack_require__.miniCssF = (chunkId) => {\n\t// return url for filenames based on template\n\treturn undefined;\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.nmd = (module) => {\n\tmodule.paths = [];\n\tif (!module.children) module.children = [];\n\treturn module;\n};","__webpack_require__.S = {};\nvar initPromises = {};\nvar initTokens = {};\n__webpack_require__.I = (name, initScope) => {\n\tif(!initScope) initScope = [];\n\t// handling circular init calls\n\tvar initToken = initTokens[name];\n\tif(!initToken) initToken = initTokens[name] = {};\n\tif(initScope.indexOf(initToken) >= 0) return;\n\tinitScope.push(initToken);\n\t// only runs once\n\tif(initPromises[name]) return initPromises[name];\n\t// creates a new share scope if needed\n\tif(!__webpack_require__.o(__webpack_require__.S, name)) __webpack_require__.S[name] = {};\n\t// runs all init snippets from all modules reachable\n\tvar scope = __webpack_require__.S[name];\n\tvar warn = (msg) => {\n\t\tif (typeof console !== \"undefined\" && console.warn) console.warn(msg);\n\t};\n\tvar uniqueName = \"signalk-edge-link\";\n\tvar register = (name, version, factory, eager) => {\n\t\tvar versions = scope[name] = scope[name] || {};\n\t\tvar activeVersion = versions[version];\n\t\tif(!activeVersion || (!activeVersion.loaded && (!eager != !activeVersion.eager ? eager : uniqueName > activeVersion.from))) versions[version] = { get: factory, from: uniqueName, eager: !!eager };\n\t};\n\tvar initExternal = (id) => {\n\t\tvar handleError = (err) => (warn(\"Initialization of sharing external failed: \" + err));\n\t\ttry {\n\t\t\tvar module = __webpack_require__(id);\n\t\t\tif(!module) return;\n\t\t\tvar initFn = (module) => (module && module.init && module.init(__webpack_require__.S[name], initScope))\n\t\t\tif(module.then) return promises.push(module.then(initFn, handleError));\n\t\t\tvar initResult = initFn(module);\n\t\t\tif(initResult && initResult.then) return promises.push(initResult['catch'](handleError));\n\t\t} catch(err) { handleError(err); }\n\t}\n\tvar promises = [];\n\tswitch(name) {\n\t\tcase \"default\": {\n\t\t\tregister(\"react\", \"16.14.0\", () => (__webpack_require__.e(540).then(() => (() => (__webpack_require__(6540))))));\n\t\t}\n\t\tbreak;\n\t}\n\tif(!promises.length) return initPromises[name] = 1;\n\treturn initPromises[name] = Promise.all(promises).then(() => (initPromises[name] = 1));\n};","var scriptUrl;\nif (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + \"\";\nvar document = __webpack_require__.g.document;\nif (!scriptUrl && document) {\n\tif (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT')\n\t\tscriptUrl = document.currentScript.src;\n\tif (!scriptUrl) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tif(scripts.length) {\n\t\t\tvar i = scripts.length - 1;\n\t\t\twhile (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src;\n\t\t}\n\t}\n}\n// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration\n// or pass an empty string (\"\") and set the __webpack_public_path__ variable from your code to use your own logic.\nif (!scriptUrl) throw new Error(\"Automatic publicPath is not supported in this browser\");\nscriptUrl = scriptUrl.replace(/^blob:/, \"\").replace(/#.*$/, \"\").replace(/\\?.*$/, \"\").replace(/\\/[^\\/]+$/, \"/\");\n__webpack_require__.p = scriptUrl;","// no baseURI\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t981: 0\n};\n\n__webpack_require__.f.j = (chunkId, promises) => {\n\t\t// JSONP chunk loading for javascript\n\t\tvar installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;\n\t\tif(installedChunkData !== 0) { // 0 means \"already installed\".\n\n\t\t\t// a Promise means \"currently loading\".\n\t\t\tif(installedChunkData) {\n\t\t\t\tpromises.push(installedChunkData[2]);\n\t\t\t} else {\n\t\t\t\tif(true) { // all chunks have JS\n\t\t\t\t\t// setup Promise in chunk cache\n\t\t\t\t\tvar promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject]));\n\t\t\t\t\tpromises.push(installedChunkData[2] = promise);\n\n\t\t\t\t\t// start chunk loading\n\t\t\t\t\tvar url = __webpack_require__.p + __webpack_require__.u(chunkId);\n\t\t\t\t\t// create error before stack unwound to get useful stacktrace later\n\t\t\t\t\tvar error = new Error();\n\t\t\t\t\tvar loadingEnded = (event) => {\n\t\t\t\t\t\tif(__webpack_require__.o(installedChunks, chunkId)) {\n\t\t\t\t\t\t\tinstalledChunkData = installedChunks[chunkId];\n\t\t\t\t\t\t\tif(installedChunkData !== 0) installedChunks[chunkId] = undefined;\n\t\t\t\t\t\t\tif(installedChunkData) {\n\t\t\t\t\t\t\t\tvar errorType = event && (event.type === 'load' ? 'missing' : event.type);\n\t\t\t\t\t\t\t\tvar realSrc = event && event.target && event.target.src;\n\t\t\t\t\t\t\t\terror.message = 'Loading chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';\n\t\t\t\t\t\t\t\terror.name = 'ChunkLoadError';\n\t\t\t\t\t\t\t\terror.type = errorType;\n\t\t\t\t\t\t\t\terror.request = realSrc;\n\t\t\t\t\t\t\t\tinstalledChunkData[1](error);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t\t__webpack_require__.l(url, loadingEnded, \"chunk-\" + chunkId, chunkId);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n};\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n// no on chunks loaded\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = (parentChunkLoadingFunction, data) => {\n\tvar [chunkIds, moreModules, runtime] = data;\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some((id) => (installedChunks[id] !== 0))) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunksignalk_edge_link\"] = self[\"webpackChunksignalk_edge_link\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));","// module cache are used so entry inlining is disabled\n// startup\n// Load entry module and return exports\nvar __webpack_exports__ = __webpack_require__(805);\n"],"names":["inProgress","dataWebpackPrefix","parseVersion","versionLt","rangeToString","satisfy","exists","get","eagerOnly","findLatestVersion","findSatisfyingVersion","getInvalidVersionMessage","fail","init","useFallback","load","loadStrictVersion","installedModules","moduleToHandlerMapping","chunkMapping","startedInstallModules","moduleMap","Promise","all","__webpack_require__","e","then","module","getScope","R","o","resolve","Error","undefined","shareScope","initScope","S","name","oldScope","I","d","exports","__webpack_module_cache__","moduleId","cachedModule","id","loaded","__webpack_modules__","m","c","n","getter","__esModule","a","definition","key","Object","defineProperty","enumerable","f","chunkId","keys","reduce","promises","u","miniCssF","g","globalThis","this","Function","window","obj","prop","prototype","hasOwnProperty","call","l","url","done","push","script","needAttach","scripts","document","getElementsByTagName","i","length","s","getAttribute","createElement","charset","nc","setAttribute","src","onScriptComplete","prev","event","onerror","onload","clearTimeout","timeout","doneFns","parentNode","removeChild","forEach","fn","setTimeout","bind","type","target","head","appendChild","r","Symbol","toStringTag","value","nmd","paths","children","initPromises","initTokens","initToken","indexOf","scope","uniqueName","version","factory","eager","versions","activeVersion","from","register","scriptUrl","importScripts","location","currentScript","tagName","toUpperCase","test","replace","p","str","split","map","exec","apply","b","t","range","pop","entry","filtered","requiredVersion","scopeName","join","msg","fallback","failAsNotExist","promise","satisfyingVersion","consumes","onFactory","onError","error","installedChunks","j","installedChunkData","reject","errorType","realSrc","message","request","webpackJsonpCallback","parentChunkLoadingFunction","data","chunkIds","moreModules","runtime","some","chunkLoadingGlobal","self","__webpack_exports__"],"sourceRoot":""}
1
+ {"version":3,"file":"remoteEntry.js","mappings":"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","sources":["webpack://signalk-edge-link/webpack/runtime/load script","webpack://signalk-edge-link/webpack/runtime/consumes","webpack://signalk-edge-link/webpack/container-entry","webpack://signalk-edge-link/webpack/bootstrap","webpack://signalk-edge-link/webpack/runtime/compat get default export","webpack://signalk-edge-link/webpack/runtime/define property getters","webpack://signalk-edge-link/webpack/runtime/ensure chunk","webpack://signalk-edge-link/webpack/runtime/get javascript chunk filename","webpack://signalk-edge-link/webpack/runtime/get mini-css chunk filename","webpack://signalk-edge-link/webpack/runtime/global","webpack://signalk-edge-link/webpack/runtime/hasOwnProperty shorthand","webpack://signalk-edge-link/webpack/runtime/make namespace object","webpack://signalk-edge-link/webpack/runtime/node module decorator","webpack://signalk-edge-link/webpack/runtime/sharing","webpack://signalk-edge-link/webpack/runtime/publicPath","webpack://signalk-edge-link/webpack/runtime/jsonp chunk loading","webpack://signalk-edge-link/webpack/startup"],"sourcesContent":["var inProgress = {};\nvar dataWebpackPrefix = \"signalk-edge-link:\";\n// loadScript function to load a script via script tag\n__webpack_require__.l = (url, done, key, chunkId) => {\n\tif(inProgress[url]) { inProgress[url].push(done); return; }\n\tvar script, needAttach;\n\tif(key !== undefined) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tfor(var i = 0; i < scripts.length; i++) {\n\t\t\tvar s = scripts[i];\n\t\t\tif(s.getAttribute(\"src\") == url || s.getAttribute(\"data-webpack\") == dataWebpackPrefix + key) { script = s; break; }\n\t\t}\n\t}\n\tif(!script) {\n\t\tneedAttach = true;\n\t\tscript = document.createElement('script');\n\n\t\tscript.charset = 'utf-8';\n\t\tif (__webpack_require__.nc) {\n\t\t\tscript.setAttribute(\"nonce\", __webpack_require__.nc);\n\t\t}\n\t\tscript.setAttribute(\"data-webpack\", dataWebpackPrefix + key);\n\n\t\tscript.src = url;\n\t}\n\tinProgress[url] = [done];\n\tvar onScriptComplete = (prev, event) => {\n\t\t// avoid mem leaks in IE.\n\t\tscript.onerror = script.onload = null;\n\t\tclearTimeout(timeout);\n\t\tvar doneFns = inProgress[url];\n\t\tdelete inProgress[url];\n\t\tscript.parentNode && script.parentNode.removeChild(script);\n\t\tdoneFns && doneFns.forEach((fn) => (fn(event)));\n\t\tif(prev) return prev(event);\n\t}\n\tvar timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000);\n\tscript.onerror = onScriptComplete.bind(null, script.onerror);\n\tscript.onload = onScriptComplete.bind(null, script.onload);\n\tneedAttach && document.head.appendChild(script);\n};","var parseVersion = (str) => {\n\t// see webpack/lib/util/semver.js for original code\n\tvar p=p=>{return p.split(\".\").map(p=>{return+p==p?+p:p})},n=/^([^-+]+)?(?:-([^+]+))?(?:\\+(.+))?$/.exec(str),r=n[1]?p(n[1]):[];return n[2]&&(r.length++,r.push.apply(r,p(n[2]))),n[3]&&(r.push([]),r.push.apply(r,p(n[3]))),r;\n}\nvar versionLt = (a, b) => {\n\t// see webpack/lib/util/semver.js for original code\n\ta=parseVersion(a),b=parseVersion(b);for(var r=0;;){if(r>=a.length)return r<b.length&&\"u\"!=(typeof b[r])[0];var e=a[r],n=(typeof e)[0];if(r>=b.length)return\"u\"==n;var t=b[r],f=(typeof t)[0];if(n!=f)return\"o\"==n&&\"n\"==f||(\"s\"==f||\"u\"==n);if(\"o\"!=n&&\"u\"!=n&&e!=t)return e<t;r++}\n}\nvar rangeToString = (range) => {\n\t// see webpack/lib/util/semver.js for original code\n\tvar r=range[0],n=\"\";if(1===range.length)return\"*\";if(r+.5){n+=0==r?\">=\":-1==r?\"<\":1==r?\"^\":2==r?\"~\":r>0?\"=\":\"!=\";for(var e=1,a=1;a<range.length;a++){e--,n+=\"u\"==(typeof(t=range[a]))[0]?\"-\":(e>0?\".\":\"\")+(e=2,t)}return n}var g=[];for(a=1;a<range.length;a++){var t=range[a];g.push(0===t?\"not(\"+o()+\")\":1===t?\"(\"+o()+\" || \"+o()+\")\":2===t?g.pop()+\" \"+g.pop():rangeToString(t))}return o();function o(){return g.pop().replace(/^\\((.+)\\)$/,\"$1\")}\n}\nvar satisfy = (range, version) => {\n\t// see webpack/lib/util/semver.js for original code\n\tif(0 in range){version=parseVersion(version);var e=range[0],r=e<0;r&&(e=-e-1);for(var n=0,i=1,a=!0;;i++,n++){var f,s,g=i<range.length?(typeof range[i])[0]:\"\";if(n>=version.length||\"o\"==(s=(typeof(f=version[n]))[0]))return!a||(\"u\"==g?i>e&&!r:\"\"==g!=r);if(\"u\"==s){if(!a||\"u\"!=g)return!1}else if(a)if(g==s)if(i<=e){if(f!=range[i])return!1}else{if(r?f>range[i]:f<range[i])return!1;f!=range[i]&&(a=!1)}else if(\"s\"!=g&&\"n\"!=g){if(r||i<=e)return!1;a=!1,i--}else{if(i<=e||s<g!=r)return!1;a=!1}else\"s\"!=g&&\"n\"!=g&&(a=!1,i--)}}var t=[],o=t.pop.bind(t);for(n=1;n<range.length;n++){var u=range[n];t.push(1==u?o()|o():2==u?o()&o():u?satisfy(u,version):!o())}return!!o();\n}\nvar exists = (scope, key) => {\n\treturn scope && __webpack_require__.o(scope, key);\n}\nvar get = (entry) => {\n\tentry.loaded = 1;\n\treturn entry.get()\n};\nvar eagerOnly = (versions) => {\n\treturn Object.keys(versions).reduce((filtered, version) => {\n\t\t\tif (versions[version].eager) {\n\t\t\t\tfiltered[version] = versions[version];\n\t\t\t}\n\t\t\treturn filtered;\n\t}, {});\n};\nvar findLatestVersion = (scope, key, eager) => {\n\tvar versions = eager ? eagerOnly(scope[key]) : scope[key];\n\tvar key = Object.keys(versions).reduce((a, b) => {\n\t\treturn !a || versionLt(a, b) ? b : a;\n\t}, 0);\n\treturn key && versions[key];\n};\nvar findSatisfyingVersion = (scope, key, requiredVersion, eager) => {\n\tvar versions = eager ? eagerOnly(scope[key]) : scope[key];\n\tvar key = Object.keys(versions).reduce((a, b) => {\n\t\tif (!satisfy(requiredVersion, b)) return a;\n\t\treturn !a || versionLt(a, b) ? b : a;\n\t}, 0);\n\treturn key && versions[key]\n};\nvar findSingletonVersionKey = (scope, key, eager) => {\n\tvar versions = eager ? eagerOnly(scope[key]) : scope[key];\n\treturn Object.keys(versions).reduce((a, b) => {\n\t\treturn !a || (!versions[a].loaded && versionLt(a, b)) ? b : a;\n\t}, 0);\n};\nvar getInvalidSingletonVersionMessage = (scope, key, version, requiredVersion) => {\n\treturn \"Unsatisfied version \" + version + \" from \" + (version && scope[key][version].from) + \" of shared singleton module \" + key + \" (required \" + rangeToString(requiredVersion) + \")\"\n};\nvar getInvalidVersionMessage = (scope, scopeName, key, requiredVersion, eager) => {\n\tvar versions = scope[key];\n\treturn \"No satisfying version (\" + rangeToString(requiredVersion) + \")\" + (eager ? \" for eager consumption\" : \"\") + \" of shared module \" + key + \" found in shared scope \" + scopeName + \".\\n\" +\n\t\t\"Available versions: \" + Object.keys(versions).map((key) => {\n\t\treturn key + \" from \" + versions[key].from;\n\t}).join(\", \");\n};\nvar fail = (msg) => {\n\tthrow new Error(msg);\n}\nvar failAsNotExist = (scopeName, key) => {\n\treturn fail(\"Shared module \" + key + \" doesn't exist in shared scope \" + scopeName);\n}\nvar warn = /*#__PURE__*/ (msg) => {\n\tif (typeof console !== \"undefined\" && console.warn) console.warn(msg);\n};\nvar init = (fn) => (function(scopeName, key, eager, c, d) {\n\tvar promise = __webpack_require__.I(scopeName);\n\tif (promise && promise.then && !eager) {\n\t\treturn promise.then(fn.bind(fn, scopeName, __webpack_require__.S[scopeName], key, false, c, d));\n\t}\n\treturn fn(scopeName, __webpack_require__.S[scopeName], key, eager, c, d);\n});\n\nvar useFallback = (scopeName, key, fallback) => {\n\treturn fallback ? fallback() : failAsNotExist(scopeName, key);\n}\nvar load = /*#__PURE__*/ init((scopeName, scope, key, eager, fallback) => {\n\tif (!exists(scope, key)) return useFallback(scopeName, key, fallback);\n\treturn get(findLatestVersion(scope, key, eager));\n});\nvar loadVersion = /*#__PURE__*/ init((scopeName, scope, key, eager, requiredVersion, fallback) => {\n\tif (!exists(scope, key)) return useFallback(scopeName, key, fallback);\n\tvar satisfyingVersion = findSatisfyingVersion(scope, key, requiredVersion, eager);\n\tif (satisfyingVersion) return get(satisfyingVersion);\n\twarn(getInvalidVersionMessage(scope, scopeName, key, requiredVersion, eager))\n\treturn get(findLatestVersion(scope, key, eager));\n});\nvar loadStrictVersion = /*#__PURE__*/ init((scopeName, scope, key, eager, requiredVersion, fallback) => {\n\tif (!exists(scope, key)) return useFallback(scopeName, key, fallback);\n\tvar satisfyingVersion = findSatisfyingVersion(scope, key, requiredVersion, eager);\n\tif (satisfyingVersion) return get(satisfyingVersion);\n\tif (fallback) return fallback();\n\tfail(getInvalidVersionMessage(scope, scopeName, key, requiredVersion, eager));\n});\nvar loadSingleton = /*#__PURE__*/ init((scopeName, scope, key, eager, fallback) => {\n\tif (!exists(scope, key)) return useFallback(scopeName, key, fallback);\n\tvar version = findSingletonVersionKey(scope, key, eager);\n\treturn get(scope[key][version]);\n});\nvar loadSingletonVersion = /*#__PURE__*/ init((scopeName, scope, key, eager, requiredVersion, fallback) => {\n\tif (!exists(scope, key)) return useFallback(scopeName, key, fallback);\n\tvar version = findSingletonVersionKey(scope, key, eager);\n\tif (!satisfy(requiredVersion, version)) {\n\t\twarn(getInvalidSingletonVersionMessage(scope, key, version, requiredVersion));\n\t}\n\treturn get(scope[key][version]);\n});\nvar loadStrictSingletonVersion = /*#__PURE__*/ init((scopeName, scope, key, eager, requiredVersion, fallback) => {\n\tif (!exists(scope, key)) return useFallback(scopeName, key, fallback);\n\tvar version = findSingletonVersionKey(scope, key, eager);\n\tif (!satisfy(requiredVersion, version)) {\n\t\tfail(getInvalidSingletonVersionMessage(scope, key, version, requiredVersion));\n\t}\n\treturn get(scope[key][version]);\n});\nvar installedModules = {};\nvar moduleToHandlerMapping = {\n\t4147: () => (loadStrictVersion(\"default\", \"react\", false, [1,16,13,1], () => (__webpack_require__.e(540).then(() => (() => (__webpack_require__(6540))))))),\n\t4167: () => (load(\"default\", \"react\", false, () => (__webpack_require__.e(540).then(() => (() => (__webpack_require__(6540))))))),\n\t7920: () => (loadStrictVersion(\"default\", \"react\", false, [,[0,17],[1,16,14,0],1], () => (__webpack_require__.e(540).then(() => (() => (__webpack_require__(6540))))))),\n\t8271: () => (loadStrictVersion(\"default\", \"react\", false, [0,0,14,0], () => (__webpack_require__.e(540).then(() => (() => (__webpack_require__(6540)))))))\n};\n// no consumes in initial chunks\nvar chunkMapping = {\n\t\"982\": [\n\t\t4147,\n\t\t4167,\n\t\t7920,\n\t\t8271\n\t]\n};\nvar startedInstallModules = {};\n__webpack_require__.f.consumes = (chunkId, promises) => {\n\tif(__webpack_require__.o(chunkMapping, chunkId)) {\n\t\tchunkMapping[chunkId].forEach((id) => {\n\t\t\tif(__webpack_require__.o(installedModules, id)) return promises.push(installedModules[id]);\n\t\t\tif(!startedInstallModules[id]) {\n\t\t\tvar onFactory = (factory) => {\n\t\t\t\tinstalledModules[id] = 0;\n\t\t\t\t__webpack_require__.m[id] = (module) => {\n\t\t\t\t\tdelete __webpack_require__.c[id];\n\t\t\t\t\tmodule.exports = factory();\n\t\t\t\t}\n\t\t\t};\n\t\t\tstartedInstallModules[id] = true;\n\t\t\tvar onError = (error) => {\n\t\t\t\tdelete installedModules[id];\n\t\t\t\t__webpack_require__.m[id] = (module) => {\n\t\t\t\t\tdelete __webpack_require__.c[id];\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t};\n\t\t\ttry {\n\t\t\t\tvar promise = moduleToHandlerMapping[id]();\n\t\t\t\tif(promise.then) {\n\t\t\t\t\tpromises.push(installedModules[id] = promise.then(onFactory)['catch'](onError));\n\t\t\t\t} else onFactory(promise);\n\t\t\t} catch(e) { onError(e); }\n\t\t\t}\n\t\t});\n\t}\n}","var moduleMap = {\n\t\"./PluginConfigurationPanel\": () => {\n\t\treturn Promise.all([__webpack_require__.e(277), __webpack_require__.e(982)]).then(() => (() => ((__webpack_require__(4353)))));\n\t}\n};\nvar get = (module, getScope) => {\n\t__webpack_require__.R = getScope;\n\tgetScope = (\n\t\t__webpack_require__.o(moduleMap, module)\n\t\t\t? moduleMap[module]()\n\t\t\t: Promise.resolve().then(() => {\n\t\t\t\tthrow new Error('Module \"' + module + '\" does not exist in container.');\n\t\t\t})\n\t);\n\t__webpack_require__.R = undefined;\n\treturn getScope;\n};\nvar init = (shareScope, initScope) => {\n\tif (!__webpack_require__.S) return;\n\tvar name = \"default\"\n\tvar oldScope = __webpack_require__.S[name];\n\tif(oldScope && oldScope !== shareScope) throw new Error(\"Container initialization failed as it has already been initialized with a different share scope\");\n\t__webpack_require__.S[name] = shareScope;\n\treturn __webpack_require__.I(name, initScope);\n};\n\n// This exports getters to disallow modifications\n__webpack_require__.d(exports, {\n\tget: () => (get),\n\tinit: () => (init)\n});","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\tid: moduleId,\n\t\tloaded: false,\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Flag the module as loaded\n\tmodule.loaded = true;\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n// expose the module cache\n__webpack_require__.c = __webpack_module_cache__;\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.f = {};\n// This file contains only the entry chunk.\n// The chunk loading function for additional chunks\n__webpack_require__.e = (chunkId) => {\n\treturn Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {\n\t\t__webpack_require__.f[key](chunkId, promises);\n\t\treturn promises;\n\t}, []));\n};","// This function allow to reference async chunks\n__webpack_require__.u = (chunkId) => {\n\t// return url for filenames based on template\n\treturn \"\" + chunkId + \".\" + {\"277\":\"99e19dcb5b778c964ace\",\"540\":\"70a0bc6aadb412703390\",\"982\":\"63949a2b2f6c5854e034\"}[chunkId] + \".js\";\n};","// This function allow to reference async chunks\n__webpack_require__.miniCssF = (chunkId) => {\n\t// return url for filenames based on template\n\treturn undefined;\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.nmd = (module) => {\n\tmodule.paths = [];\n\tif (!module.children) module.children = [];\n\treturn module;\n};","__webpack_require__.S = {};\nvar initPromises = {};\nvar initTokens = {};\n__webpack_require__.I = (name, initScope) => {\n\tif(!initScope) initScope = [];\n\t// handling circular init calls\n\tvar initToken = initTokens[name];\n\tif(!initToken) initToken = initTokens[name] = {};\n\tif(initScope.indexOf(initToken) >= 0) return;\n\tinitScope.push(initToken);\n\t// only runs once\n\tif(initPromises[name]) return initPromises[name];\n\t// creates a new share scope if needed\n\tif(!__webpack_require__.o(__webpack_require__.S, name)) __webpack_require__.S[name] = {};\n\t// runs all init snippets from all modules reachable\n\tvar scope = __webpack_require__.S[name];\n\tvar warn = (msg) => {\n\t\tif (typeof console !== \"undefined\" && console.warn) console.warn(msg);\n\t};\n\tvar uniqueName = \"signalk-edge-link\";\n\tvar register = (name, version, factory, eager) => {\n\t\tvar versions = scope[name] = scope[name] || {};\n\t\tvar activeVersion = versions[version];\n\t\tif(!activeVersion || (!activeVersion.loaded && (!eager != !activeVersion.eager ? eager : uniqueName > activeVersion.from))) versions[version] = { get: factory, from: uniqueName, eager: !!eager };\n\t};\n\tvar initExternal = (id) => {\n\t\tvar handleError = (err) => (warn(\"Initialization of sharing external failed: \" + err));\n\t\ttry {\n\t\t\tvar module = __webpack_require__(id);\n\t\t\tif(!module) return;\n\t\t\tvar initFn = (module) => (module && module.init && module.init(__webpack_require__.S[name], initScope))\n\t\t\tif(module.then) return promises.push(module.then(initFn, handleError));\n\t\t\tvar initResult = initFn(module);\n\t\t\tif(initResult && initResult.then) return promises.push(initResult['catch'](handleError));\n\t\t} catch(err) { handleError(err); }\n\t}\n\tvar promises = [];\n\tswitch(name) {\n\t\tcase \"default\": {\n\t\t\tregister(\"react\", \"16.14.0\", () => (__webpack_require__.e(540).then(() => (() => (__webpack_require__(6540))))));\n\t\t}\n\t\tbreak;\n\t}\n\tif(!promises.length) return initPromises[name] = 1;\n\treturn initPromises[name] = Promise.all(promises).then(() => (initPromises[name] = 1));\n};","var scriptUrl;\nif (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + \"\";\nvar document = __webpack_require__.g.document;\nif (!scriptUrl && document) {\n\tif (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT')\n\t\tscriptUrl = document.currentScript.src;\n\tif (!scriptUrl) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tif(scripts.length) {\n\t\t\tvar i = scripts.length - 1;\n\t\t\twhile (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src;\n\t\t}\n\t}\n}\n// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration\n// or pass an empty string (\"\") and set the __webpack_public_path__ variable from your code to use your own logic.\nif (!scriptUrl) throw new Error(\"Automatic publicPath is not supported in this browser\");\nscriptUrl = scriptUrl.replace(/^blob:/, \"\").replace(/#.*$/, \"\").replace(/\\?.*$/, \"\").replace(/\\/[^\\/]+$/, \"/\");\n__webpack_require__.p = scriptUrl;","// no baseURI\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t981: 0\n};\n\n__webpack_require__.f.j = (chunkId, promises) => {\n\t\t// JSONP chunk loading for javascript\n\t\tvar installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;\n\t\tif(installedChunkData !== 0) { // 0 means \"already installed\".\n\n\t\t\t// a Promise means \"currently loading\".\n\t\t\tif(installedChunkData) {\n\t\t\t\tpromises.push(installedChunkData[2]);\n\t\t\t} else {\n\t\t\t\tif(true) { // all chunks have JS\n\t\t\t\t\t// setup Promise in chunk cache\n\t\t\t\t\tvar promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject]));\n\t\t\t\t\tpromises.push(installedChunkData[2] = promise);\n\n\t\t\t\t\t// start chunk loading\n\t\t\t\t\tvar url = __webpack_require__.p + __webpack_require__.u(chunkId);\n\t\t\t\t\t// create error before stack unwound to get useful stacktrace later\n\t\t\t\t\tvar error = new Error();\n\t\t\t\t\tvar loadingEnded = (event) => {\n\t\t\t\t\t\tif(__webpack_require__.o(installedChunks, chunkId)) {\n\t\t\t\t\t\t\tinstalledChunkData = installedChunks[chunkId];\n\t\t\t\t\t\t\tif(installedChunkData !== 0) installedChunks[chunkId] = undefined;\n\t\t\t\t\t\t\tif(installedChunkData) {\n\t\t\t\t\t\t\t\tvar errorType = event && (event.type === 'load' ? 'missing' : event.type);\n\t\t\t\t\t\t\t\tvar realSrc = event && event.target && event.target.src;\n\t\t\t\t\t\t\t\terror.message = 'Loading chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';\n\t\t\t\t\t\t\t\terror.name = 'ChunkLoadError';\n\t\t\t\t\t\t\t\terror.type = errorType;\n\t\t\t\t\t\t\t\terror.request = realSrc;\n\t\t\t\t\t\t\t\tinstalledChunkData[1](error);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t\t__webpack_require__.l(url, loadingEnded, \"chunk-\" + chunkId, chunkId);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n};\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n// no on chunks loaded\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = (parentChunkLoadingFunction, data) => {\n\tvar [chunkIds, moreModules, runtime] = data;\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some((id) => (installedChunks[id] !== 0))) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunksignalk_edge_link\"] = self[\"webpackChunksignalk_edge_link\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));","// module cache are used so entry inlining is disabled\n// startup\n// Load entry module and return exports\nvar __webpack_exports__ = __webpack_require__(805);\n"],"names":["inProgress","dataWebpackPrefix","parseVersion","versionLt","rangeToString","satisfy","exists","get","eagerOnly","findLatestVersion","findSatisfyingVersion","getInvalidVersionMessage","fail","init","useFallback","load","loadStrictVersion","installedModules","moduleToHandlerMapping","chunkMapping","startedInstallModules","moduleMap","Promise","all","__webpack_require__","e","then","module","getScope","R","o","resolve","Error","undefined","shareScope","initScope","S","name","oldScope","I","d","exports","__webpack_module_cache__","moduleId","cachedModule","id","loaded","__webpack_modules__","m","c","n","getter","__esModule","a","definition","key","Object","defineProperty","enumerable","f","chunkId","keys","reduce","promises","u","miniCssF","g","globalThis","this","Function","window","obj","prop","prototype","hasOwnProperty","call","l","url","done","push","script","needAttach","scripts","document","getElementsByTagName","i","length","s","getAttribute","createElement","charset","nc","setAttribute","src","onScriptComplete","prev","event","onerror","onload","clearTimeout","timeout","doneFns","parentNode","removeChild","forEach","fn","setTimeout","bind","type","target","head","appendChild","r","Symbol","toStringTag","value","nmd","paths","children","initPromises","initTokens","initToken","indexOf","scope","uniqueName","version","factory","eager","versions","activeVersion","from","register","scriptUrl","importScripts","location","currentScript","tagName","toUpperCase","test","replace","p","str","split","map","exec","apply","b","t","range","pop","entry","filtered","requiredVersion","scopeName","join","msg","fallback","failAsNotExist","promise","satisfyingVersion","consumes","onFactory","onError","error","installedChunks","j","installedChunkData","reject","errorType","realSrc","message","request","webpackJsonpCallback","parentChunkLoadingFunction","data","chunkIds","moreModules","runtime","some","chunkLoadingGlobal","self","__webpack_exports__"],"sourceRoot":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"982.b207a377ed6542e2fb4a.js","mappings":"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","sources":["webpack://signalk-edge-link/./src/webapp/utils/apiFetch.ts","webpack://signalk-edge-link/./src/shared/crypto-constants.ts","webpack://signalk-edge-link/./src/shared/connection-schema.ts","webpack://signalk-edge-link/./src/webapp/components/PluginConfigurationPanel.tsx"],"sourcesContent":["/// <reference lib=\"dom\" />\r\n\r\nexport const MANAGEMENT_TOKEN_ERROR_MESSAGE = \"Management token required/invalid.\";\r\n\r\ninterface AuthConfig {\r\n token: string | null;\r\n localStorageKey: string;\r\n queryParam: string;\r\n includeTokenInQuery: boolean;\r\n headerMode: string;\r\n}\r\n\r\ndeclare global {\r\n interface Window {\r\n __EDGE_LINK_AUTH__?: Partial<AuthConfig>;\r\n }\r\n}\r\n\r\nconst DEFAULT_AUTH_CONFIG: AuthConfig = {\r\n token: null,\r\n localStorageKey: \"signalkEdgeLinkManagementToken\",\r\n queryParam: \"edgeLinkToken\",\r\n // Default to false: query-parameter tokens leak into browser history, server\r\n // access logs, and Referer headers. Set includeTokenInQuery: true in\r\n // window.__EDGE_LINK_AUTH__ only when you explicitly need URL-based auth.\r\n includeTokenInQuery: false,\r\n headerMode: \"both\"\r\n};\r\n\r\nfunction readRuntimeAuthConfig(): AuthConfig {\r\n if (typeof window === \"undefined\") {\r\n return DEFAULT_AUTH_CONFIG;\r\n }\r\n\r\n const runtime = window.__EDGE_LINK_AUTH__;\r\n if (!runtime || typeof runtime !== \"object\") {\r\n return DEFAULT_AUTH_CONFIG;\r\n }\r\n\r\n return { ...DEFAULT_AUTH_CONFIG, ...runtime };\r\n}\r\n\r\nfunction resolveToken(config: AuthConfig): string {\r\n if (config.token) {\r\n return String(config.token).trim();\r\n }\r\n\r\n if (typeof window === \"undefined\") {\r\n return \"\";\r\n }\r\n\r\n // SECURITY NOTE: Query parameter tokens can leak into browser history, server\r\n // access logs, and Referer headers. Prefer localStorage or\r\n // window.__EDGE_LINK_AUTH__.token for production deployments. Set\r\n // includeTokenInQuery: false in __EDGE_LINK_AUTH__ to disable this path.\r\n if (config.includeTokenInQuery && config.queryParam) {\r\n const tokenFromQuery = new URLSearchParams(window.location.search).get(config.queryParam);\r\n if (tokenFromQuery) {\r\n return tokenFromQuery.trim();\r\n }\r\n }\r\n\r\n if (config.localStorageKey && window.localStorage) {\r\n const tokenFromStorage = window.localStorage.getItem(config.localStorageKey);\r\n if (tokenFromStorage) {\r\n return tokenFromStorage.trim();\r\n }\r\n }\r\n\r\n return \"\";\r\n}\r\n\r\nfunction attachAuthHeaders(headers: Headers, token: string, headerMode: string): Headers {\r\n if (!token) {\r\n return headers;\r\n }\r\n\r\n const normalizedMode = (headerMode || \"both\").toLowerCase();\r\n if (\r\n normalizedMode === \"x-edge-link-token\" ||\r\n normalizedMode === \"token\" ||\r\n normalizedMode === \"both\"\r\n ) {\r\n headers.set(\"X-Edge-Link-Token\", token);\r\n }\r\n if (\r\n normalizedMode === \"authorization\" ||\r\n normalizedMode === \"bearer\" ||\r\n normalizedMode === \"both\"\r\n ) {\r\n headers.set(\"Authorization\", `Bearer ${token}`);\r\n }\r\n return headers;\r\n}\r\n\r\nexport function getAuthToken(): string {\r\n const config = readRuntimeAuthConfig();\r\n return resolveToken(config);\r\n}\r\n\r\nexport function getTokenHelpText(): string {\r\n const config = readRuntimeAuthConfig();\r\n const modeText =\r\n config.headerMode && String(config.headerMode).toLowerCase() === \"authorization\"\r\n ? \"Authorization: Bearer <token>\"\r\n : config.headerMode && String(config.headerMode).toLowerCase() === \"x-edge-link-token\"\r\n ? \"X-Edge-Link-Token\"\r\n : \"X-Edge-Link-Token and Authorization: Bearer <token>\";\r\n\r\n return `The server-side token is configured in plugin settings (managementApiToken) or via the SIGNALK_EDGE_LINK_MANAGEMENT_TOKEN environment variable. To authenticate from the browser, provide the token using window.__EDGE_LINK_AUTH__.token, query parameter \"${config.queryParam}\", or localStorage key \"${config.localStorageKey}\". Requests send ${modeText} when a token is available.`;\r\n}\r\n\r\nexport function apiFetch(input: string | Request, init: RequestInit = {}): Promise<Response> {\r\n const config = readRuntimeAuthConfig();\r\n const token = resolveToken(config);\r\n const headers = new Headers(init.headers || {});\r\n attachAuthHeaders(headers, token, config.headerMode);\r\n\r\n return fetch(input, {\r\n ...init,\r\n headers\r\n });\r\n}\r\n","\"use strict\";\r\n\r\n/**\r\n * Shared crypto constants that must stay in sync between the backend crypto\r\n * module and any UI copy that describes key-derivation behaviour. Kept under\r\n * `src/shared/` so both the server-side build and the webapp bundle can\r\n * reference the same value.\r\n */\r\n\r\n/**\r\n * PBKDF2-SHA256 iteration count used by {@link deriveKeyFromPassphrase} and\r\n * by the opt-in 32-char ASCII key stretching path in {@link normalizeKey}.\r\n *\r\n * Tuned to the NIST SP 800-132 recommendation (≥ 600,000) and takes roughly\r\n * ~300 ms on modern server hardware. The derived key is cached per-process\r\n * so the cost is paid at most once per unique (key, salt) pair.\r\n */\r\nexport const PBKDF2_ITERATIONS = 600_000;\r\n","/**\r\n * Single source of truth for the connection configuration schema.\r\n *\r\n * Both the backend `plugin.schema` in `src/index.ts` (used by Signal K's\r\n * default admin UI and served via the `/plugin-schema` route for default\r\n * extraction) and the frontend RJSF form in\r\n * `src/webapp/components/PluginConfigurationPanel.tsx` consume the fragments\r\n * exported here. Adding or editing a connection field must happen in this\r\n * module; the two consumers then render it identically.\r\n *\r\n * The fragments are typed as plain `Record<string, unknown>` so they can be\r\n * imported by both the server-side TypeScript build and the webapp build\r\n * without pulling `@rjsf/utils` into the server bundle. The webapp casts\r\n * results to `RJSFSchema` at call sites.\r\n */\r\n\r\nimport { PBKDF2_ITERATIONS } from \"./crypto-constants\";\r\n\r\nexport type SchemaFragment = Record<string, unknown>;\r\n\r\n// ── Common (client + server) ──────────────────────────────────────────────────\r\n\r\nexport const commonConnectionProperties: Record<string, SchemaFragment> = {\r\n name: {\r\n type: \"string\",\r\n title: \"Connection Name\",\r\n description:\r\n \"Human-readable label for this connection (e.g. 'Shore Server', 'Sat Client'). Used to namespace config files and Signal K metrics paths.\",\r\n default: \"connection\",\r\n maxLength: 40\r\n },\r\n serverType: {\r\n type: \"string\",\r\n title: \"Operation Mode\",\r\n description: \"Select Server to receive data, or Client to send data.\",\r\n default: \"client\",\r\n oneOf: [\r\n { const: \"server\", title: \"Server Mode – Receive Data\" },\r\n { const: \"client\", title: \"Client Mode – Send Data\" }\r\n ]\r\n },\r\n udpPort: {\r\n type: \"number\",\r\n title: \"UDP Port\",\r\n description: \"UDP port for data transmission (must match on both ends).\",\r\n default: 4446,\r\n minimum: 1024,\r\n maximum: 65535\r\n },\r\n secretKey: {\r\n type: \"string\",\r\n title: \"Encryption Key\",\r\n description:\r\n \"32-byte secret key: 32-character ASCII, 64-character hex, or 44-character base64.\",\r\n minLength: 32,\r\n maxLength: 64,\r\n pattern: \"^(?:.{32}|[0-9a-fA-F]{64}|[A-Za-z0-9+/]{43}=?)$\"\r\n },\r\n stretchAsciiKey: {\r\n type: \"boolean\",\r\n title: \"Stretch 32-char ASCII Key (PBKDF2)\",\r\n description: `When the secretKey is 32-character ASCII, route it through PBKDF2-SHA256 (${PBKDF2_ITERATIONS.toLocaleString(\"en-US\")} iterations) to raise it to full 256-bit AES strength. Hex and base64 keys are unaffected. BOTH ENDS OF THE CONNECTION MUST USE THE SAME SETTING — otherwise authentication will fail and every packet will be dropped.`,\r\n default: false\r\n },\r\n useMsgpack: {\r\n type: \"boolean\",\r\n title: \"Use MessagePack\",\r\n description: \"Binary serialization for smaller payloads (must match on both ends).\",\r\n default: false\r\n },\r\n usePathDictionary: {\r\n type: \"boolean\",\r\n title: \"Use Path Dictionary\",\r\n description: \"Encode paths as numeric IDs for bandwidth savings (must match on both ends).\",\r\n default: false\r\n },\r\n protocolVersion: {\r\n type: \"number\",\r\n title: \"Protocol Version\",\r\n description:\r\n \"v1: encrypted UDP. v2 adds reliable delivery and metrics. v3 keeps the v2 data path and authenticates control packets (ACK/NAK/HEARTBEAT/HELLO). Must match on both ends.\",\r\n default: 1,\r\n oneOf: [\r\n { const: 1, title: \"v1 – Standard encrypted UDP\" },\r\n { const: 2, title: \"v2 – Reliability, congestion control, bonding, metrics\" },\r\n { const: 3, title: \"v3 - v2 features with authenticated control packets\" }\r\n ]\r\n }\r\n};\r\n\r\n// ── Client-only transport / reachability fields ───────────────────────────────\r\n\r\nexport const clientTransportProperties: Record<string, SchemaFragment> = {\r\n udpAddress: {\r\n type: \"string\",\r\n title: \"Server Address\",\r\n description: \"IP address or hostname of the remote Signal K endpoint.\",\r\n default: \"127.0.0.1\"\r\n },\r\n helloMessageSender: {\r\n type: \"integer\",\r\n title: \"Heartbeat Interval (seconds)\",\r\n description: \"Send periodic heartbeat messages to keep NAT/firewall mappings alive.\",\r\n default: 60,\r\n minimum: 10,\r\n maximum: 3600\r\n },\r\n testAddress: {\r\n type: \"string\",\r\n title: \"Connectivity Test Address\",\r\n description: \"Host used for reachability checks (e.g. 8.8.8.8).\",\r\n default: \"127.0.0.1\"\r\n },\r\n testPort: {\r\n type: \"number\",\r\n title: \"Connectivity Test Port\",\r\n description: \"Port used for reachability checks (e.g. 53, 80, or 443).\",\r\n default: 80,\r\n minimum: 1,\r\n maximum: 65535\r\n },\r\n pingIntervalTime: {\r\n type: \"number\",\r\n title: \"Check Interval (minutes)\",\r\n description: \"Frequency of network reachability checks.\",\r\n default: 1,\r\n minimum: 0.1,\r\n maximum: 60\r\n },\r\n heartbeatInterval: {\r\n type: \"number\",\r\n title: \"NAT Keepalive Heartbeat Interval (ms)\",\r\n description:\r\n \"v2/v3 only. How often to send UDP heartbeat packets for NAT traversal. Typical NAT timeouts range from 30s to 120s.\",\r\n default: 25000,\r\n minimum: 5000,\r\n maximum: 120000\r\n }\r\n};\r\n\r\n// ── v2/v3 reliability (client pipeline — retransmit queue) ────────────────────\r\n\r\nexport const clientReliabilityProperty: SchemaFragment = {\r\n type: \"object\",\r\n title: \"Reliability Settings (v2/v3 only)\",\r\n description:\r\n \"Requires Protocol v2 or v3. Controls retransmit queue behavior and packet retry limits.\",\r\n properties: {\r\n retransmitQueueSize: {\r\n type: \"number\",\r\n title: \"Retransmit Queue Size\",\r\n description: \"Maximum number of sent packets stored for potential retransmission.\",\r\n default: 5000,\r\n minimum: 100,\r\n maximum: 50000\r\n },\r\n maxRetransmits: {\r\n type: \"number\",\r\n title: \"Max Retransmit Attempts\",\r\n description: \"Maximum resend attempts before a packet is dropped from the retransmit queue.\",\r\n default: 3,\r\n minimum: 1,\r\n maximum: 20\r\n },\r\n retransmitMaxAge: {\r\n type: \"number\",\r\n title: \"Retransmit Max Age (ms)\",\r\n description: \"Expire stale unacknowledged packets older than this age.\",\r\n default: 120000,\r\n minimum: 1000,\r\n maximum: 300000\r\n },\r\n retransmitMinAge: {\r\n type: \"number\",\r\n title: \"Retransmit Min Age (ms)\",\r\n description: \"Minimum packet age before expiration is allowed.\",\r\n default: 10000,\r\n minimum: 200,\r\n maximum: 30000\r\n },\r\n retransmitRttMultiplier: {\r\n type: \"number\",\r\n title: \"RTT Expiry Multiplier\",\r\n description: \"Dynamic expiry age = RTT × this multiplier.\",\r\n default: 12,\r\n minimum: 2,\r\n maximum: 20\r\n },\r\n ackIdleDrainAge: {\r\n type: \"number\",\r\n title: \"ACK Idle Drain Age (ms)\",\r\n description: \"If ACKs are idle longer than this, expiry becomes more aggressive.\",\r\n default: 20000,\r\n minimum: 500,\r\n maximum: 30000\r\n },\r\n forceDrainAfterAckIdle: {\r\n type: \"boolean\",\r\n title: \"Force Drain After ACK Idle\",\r\n description: \"When enabled, clear retransmit queue if no ACKs arrive for too long.\",\r\n default: false\r\n },\r\n forceDrainAfterMs: {\r\n type: \"number\",\r\n title: \"Force Drain Timeout (ms)\",\r\n description: \"ACK idle duration before force-draining retransmit queue to zero.\",\r\n default: 45000,\r\n minimum: 2000,\r\n maximum: 120000\r\n },\r\n recoveryBurstEnabled: {\r\n type: \"boolean\",\r\n title: \"Recovery Burst Enabled\",\r\n description: \"When ACKs return after outage, rapidly retransmit queued packets to catch up.\",\r\n default: true\r\n },\r\n recoveryBurstSize: {\r\n type: \"number\",\r\n title: \"Recovery Burst Size\",\r\n description: \"Max queued packets to retransmit per recovery burst cycle.\",\r\n default: 100,\r\n minimum: 10,\r\n maximum: 1000\r\n },\r\n recoveryBurstIntervalMs: {\r\n type: \"number\",\r\n title: \"Recovery Burst Interval (ms)\",\r\n description: \"Interval between recovery burst cycles while backlog exists.\",\r\n default: 200,\r\n minimum: 50,\r\n maximum: 5000\r\n },\r\n recoveryAckGapMs: {\r\n type: \"number\",\r\n title: \"Recovery ACK Gap (ms)\",\r\n description: \"Minimum ACK silence before triggering fast recovery bursts.\",\r\n default: 4000,\r\n minimum: 500,\r\n maximum: 120000\r\n }\r\n }\r\n};\r\n\r\n// ── v2/v3 reliability (server pipeline — ACK/NAK timing) ──────────────────────\r\n\r\nexport const serverReliabilityProperty: SchemaFragment = {\r\n type: \"object\",\r\n title: \"Reliability Settings (v2/v3 only)\",\r\n description: \"Requires Protocol v2 or v3. Controls ACK/NAK timing for reliable delivery.\",\r\n properties: {\r\n ackInterval: {\r\n type: \"number\",\r\n title: \"ACK Interval (ms)\",\r\n description: \"How often server sends cumulative ACK updates.\",\r\n default: 100,\r\n minimum: 20,\r\n maximum: 5000\r\n },\r\n ackResendInterval: {\r\n type: \"number\",\r\n title: \"ACK Resend Interval (ms)\",\r\n description: \"Re-send duplicate ACK periodically to recover from lost ACK packets.\",\r\n default: 1000,\r\n minimum: 100,\r\n maximum: 10000\r\n },\r\n nakTimeout: {\r\n type: \"number\",\r\n title: \"NAK Timeout (ms)\",\r\n description: \"Delay before requesting retransmission for missing sequence numbers.\",\r\n default: 100,\r\n minimum: 20,\r\n maximum: 5000\r\n }\r\n }\r\n};\r\n\r\n// ── v2/v3 congestion control (client) ─────────────────────────────────────────\r\n\r\nexport const congestionControlProperty: SchemaFragment = {\r\n type: \"object\",\r\n title: \"Dynamic Congestion Control (v2/v3 only)\",\r\n description:\r\n \"Requires Protocol v2 or v3. AIMD algorithm to dynamically adjust send rate based on network conditions.\",\r\n properties: {\r\n enabled: {\r\n type: \"boolean\",\r\n title: \"Enable Congestion Control\",\r\n description: \"Automatically adjust delta timer based on RTT and packet loss.\",\r\n default: false\r\n },\r\n targetRTT: {\r\n type: \"number\",\r\n title: \"Target RTT (ms)\",\r\n description: \"RTT threshold above which send rate is reduced.\",\r\n default: 200,\r\n minimum: 50,\r\n maximum: 2000\r\n },\r\n nominalDeltaTimer: {\r\n type: \"number\",\r\n title: \"Nominal Delta Timer (ms)\",\r\n description: \"Preferred steady-state send interval.\",\r\n default: 1000,\r\n minimum: 100,\r\n maximum: 10000\r\n },\r\n minDeltaTimer: {\r\n type: \"number\",\r\n title: \"Minimum Delta Timer (ms)\",\r\n description: \"Fastest allowed send interval.\",\r\n default: 100,\r\n minimum: 50,\r\n maximum: 1000\r\n },\r\n maxDeltaTimer: {\r\n type: \"number\",\r\n title: \"Maximum Delta Timer (ms)\",\r\n description: \"Slowest allowed send interval.\",\r\n default: 5000,\r\n minimum: 1000,\r\n maximum: 30000\r\n }\r\n }\r\n};\r\n\r\n// ── v2/v3 connection bonding (client) ─────────────────────────────────────────\r\n\r\nexport const bondingProperty: SchemaFragment = {\r\n type: \"object\",\r\n title: \"Connection Bonding (v2/v3 only)\",\r\n description:\r\n \"Requires Protocol v2 or v3. Dual-link bonding with automatic failover between primary and backup connections.\",\r\n properties: {\r\n enabled: {\r\n type: \"boolean\",\r\n title: \"Enable Connection Bonding\",\r\n description: \"Enable dual-link bonding with automatic failover.\",\r\n default: false\r\n },\r\n mode: {\r\n type: \"string\",\r\n title: \"Bonding Mode\",\r\n description: \"Bonding operating mode.\",\r\n default: \"main-backup\",\r\n oneOf: [\r\n {\r\n const: \"main-backup\",\r\n title: \"Main/Backup – Failover to backup when primary degrades\"\r\n }\r\n ]\r\n },\r\n primary: {\r\n type: \"object\",\r\n title: \"Primary Link\",\r\n description: \"Primary connection (e.g. LTE modem).\",\r\n properties: {\r\n address: { type: \"string\", title: \"Server Address\", default: \"127.0.0.1\" },\r\n port: {\r\n type: \"number\",\r\n title: \"UDP Port\",\r\n default: 4446,\r\n minimum: 1024,\r\n maximum: 65535\r\n },\r\n interface: {\r\n type: \"string\",\r\n title: \"Bind Interface (optional)\",\r\n description: \"Network interface IP to bind to.\"\r\n }\r\n }\r\n },\r\n backup: {\r\n type: \"object\",\r\n title: \"Backup Link\",\r\n description: \"Backup connection (e.g. Starlink, satellite).\",\r\n properties: {\r\n address: { type: \"string\", title: \"Server Address\", default: \"127.0.0.1\" },\r\n port: {\r\n type: \"number\",\r\n title: \"UDP Port\",\r\n default: 4447,\r\n minimum: 1024,\r\n maximum: 65535\r\n },\r\n interface: {\r\n type: \"string\",\r\n title: \"Bind Interface (optional)\",\r\n description: \"Network interface IP to bind to.\"\r\n }\r\n }\r\n },\r\n failover: {\r\n type: \"object\",\r\n title: \"Failover Thresholds\",\r\n description: \"Configure when failover is triggered.\",\r\n properties: {\r\n rttThreshold: {\r\n type: \"number\",\r\n title: \"RTT Threshold (ms)\",\r\n default: 500,\r\n minimum: 100,\r\n maximum: 5000\r\n },\r\n lossThreshold: {\r\n type: \"number\",\r\n title: \"Packet Loss Threshold (0-1)\",\r\n default: 0.1,\r\n minimum: 0.01,\r\n maximum: 0.5\r\n },\r\n healthCheckInterval: {\r\n type: \"number\",\r\n title: \"Health Check Interval (ms)\",\r\n default: 1000,\r\n minimum: 500,\r\n maximum: 10000\r\n },\r\n failbackDelay: {\r\n type: \"number\",\r\n title: \"Failback Delay (ms)\",\r\n default: 30000,\r\n minimum: 5000,\r\n maximum: 300000\r\n },\r\n heartbeatTimeout: {\r\n type: \"number\",\r\n title: \"Heartbeat Timeout (ms)\",\r\n default: 5000,\r\n minimum: 1000,\r\n maximum: 30000\r\n }\r\n }\r\n }\r\n }\r\n};\r\n\r\n// ── Client-only notifications toggle ──────────────────────────────────────────\r\n\r\nexport const enableNotificationsProperty: SchemaFragment = {\r\n type: \"boolean\",\r\n title: \"Enable Signal K Notifications\",\r\n description: \"Emit Signal K notifications for alerts and failover events.\",\r\n default: false\r\n};\r\n\r\n// ── v2/v3 monitoring alert thresholds (client) ────────────────────────────────\r\n\r\nexport const alertThresholdsProperty: SchemaFragment = {\r\n type: \"object\",\r\n title: \"Monitoring Alert Thresholds (v2/v3 only)\",\r\n description: \"Customize warning/critical thresholds for network monitoring alerts.\",\r\n properties: {\r\n rtt: {\r\n type: \"object\",\r\n title: \"RTT Thresholds\",\r\n properties: {\r\n warning: { type: \"number\", title: \"Warning RTT (ms)\", default: 300 },\r\n critical: { type: \"number\", title: \"Critical RTT (ms)\", default: 800 }\r\n }\r\n },\r\n packetLoss: {\r\n type: \"object\",\r\n title: \"Packet Loss Thresholds\",\r\n properties: {\r\n warning: { type: \"number\", title: \"Warning Loss Ratio\", default: 0.03 },\r\n critical: { type: \"number\", title: \"Critical Loss Ratio\", default: 0.1 }\r\n }\r\n },\r\n retransmitRate: {\r\n type: \"object\",\r\n title: \"Retransmit Rate Thresholds\",\r\n properties: {\r\n warning: { type: \"number\", title: \"Warning Retransmit Ratio\", default: 0.05 },\r\n critical: { type: \"number\", title: \"Critical Retransmit Ratio\", default: 0.15 }\r\n }\r\n },\r\n jitter: {\r\n type: \"object\",\r\n title: \"Jitter Thresholds\",\r\n properties: {\r\n warning: { type: \"number\", title: \"Warning Jitter (ms)\", default: 100 },\r\n critical: { type: \"number\", title: \"Critical Jitter (ms)\", default: 300 }\r\n }\r\n },\r\n queueDepth: {\r\n type: \"object\",\r\n title: \"Queue Depth Thresholds\",\r\n properties: {\r\n warning: { type: \"number\", title: \"Warning Queue Depth\", default: 100 },\r\n critical: { type: \"number\", title: \"Critical Queue Depth\", default: 500 }\r\n }\r\n }\r\n }\r\n};\r\n\r\n// ── Builder consumed by the backend (`plugin.schema` in src/index.ts) ─────────\r\n\r\n/**\r\n * Build the `connections[]` item schema used by Signal K's default admin UI\r\n * and served via `GET /plugin-schema`. Client-only fields live under\r\n * `dependencies.serverType.oneOf` so they appear only in client mode.\r\n */\r\nexport function buildConnectionItemSchema(): SchemaFragment {\r\n return {\r\n type: \"object\",\r\n title: \"Connection\",\r\n required: [\"serverType\", \"udpPort\", \"secretKey\"],\r\n properties: { ...commonConnectionProperties },\r\n dependencies: {\r\n serverType: {\r\n oneOf: [\r\n {\r\n properties: {\r\n serverType: { enum: [\"server\"] },\r\n reliability: serverReliabilityProperty\r\n }\r\n },\r\n {\r\n properties: {\r\n serverType: { enum: [\"client\"] },\r\n ...clientTransportProperties,\r\n reliability: clientReliabilityProperty,\r\n congestionControl: congestionControlProperty,\r\n bonding: bondingProperty,\r\n enableNotifications: enableNotificationsProperty,\r\n alertThresholds: alertThresholdsProperty\r\n },\r\n required: [\"udpAddress\", \"testAddress\", \"testPort\"]\r\n }\r\n ]\r\n }\r\n }\r\n };\r\n}\r\n\r\n// ── Builder consumed by the webapp (PluginConfigurationPanel.tsx) ─────────────\r\n\r\n/**\r\n * Build the flat per-connection schema consumed by the webapp RJSF form.\r\n * Unlike the backend variant this is a flat object that is rebuilt whenever\r\n * the user toggles `serverType` or `protocolVersion` so RJSF re-renders with\r\n * the right subset of fields.\r\n */\r\nexport function buildWebappConnectionSchema(\r\n isClient: boolean,\r\n protocolVersion: number | undefined\r\n): SchemaFragment {\r\n const isReliableProtocol = Number(protocolVersion) >= 2;\r\n const props: Record<string, SchemaFragment> = { ...commonConnectionProperties };\r\n const required = [\"serverType\", \"udpPort\", \"secretKey\"];\r\n\r\n if (isClient) {\r\n Object.assign(props, clientTransportProperties);\r\n props.enableNotifications = enableNotificationsProperty;\r\n required.push(\"udpAddress\", \"testAddress\", \"testPort\");\r\n if (isReliableProtocol) {\r\n props.reliability = clientReliabilityProperty;\r\n props.congestionControl = congestionControlProperty;\r\n props.bonding = bondingProperty;\r\n props.alertThresholds = alertThresholdsProperty;\r\n }\r\n } else if (isReliableProtocol) {\r\n props.reliability = serverReliabilityProperty;\r\n }\r\n\r\n return { type: \"object\", required, properties: props };\r\n}\r\n","import React from 'react';\r\nimport { useState, useEffect, useCallback, useRef } from 'react';\r\nimport Form from \"@rjsf/core\";\r\nimport validator from \"@rjsf/validator-ajv8\";\r\nimport { RJSFSchema, UiSchema, getDefaultFormState } from \"@rjsf/utils\";\r\nimport { apiFetch, MANAGEMENT_TOKEN_ERROR_MESSAGE } from \"../utils/apiFetch\";\r\nimport { buildWebappConnectionSchema } from \"../../shared/connection-schema\";\r\n\r\nconst API_BASE = \"/plugins/signalk-edge-link\";\r\n\r\n// ── Stable ID helper ──────────────────────────────────────────────────────────\r\n// Each connection object carries a frontend-only `_id` for use as React key.\r\n// It is stripped before the array is POSTed to the backend.\r\n\r\nlet _idSeq = 0;\r\nfunction makeId(): string { return `skel-${Date.now()}-${++_idSeq}`; }\r\n\r\n// ── Types ─────────────────────────────────────────────────────────────────────\r\n\r\ninterface ConnectionData {\r\n _id: string;\r\n name?: string;\r\n serverType?: string;\r\n udpPort?: number;\r\n secretKey?: string;\r\n stretchAsciiKey?: boolean;\r\n useMsgpack?: boolean;\r\n usePathDictionary?: boolean;\r\n enableNotifications?: boolean;\r\n protocolVersion?: number;\r\n udpAddress?: string;\r\n helloMessageSender?: number;\r\n testAddress?: string;\r\n testPort?: number;\r\n pingIntervalTime?: number;\r\n [key: string]: unknown;\r\n}\r\n\r\ninterface SaveStatus {\r\n type: \"saving\" | \"success\" | \"error\";\r\n message: string;\r\n}\r\n\r\n// ── Default config factories ──────────────────────────────────────────────────\r\n\r\nfunction defaultClientConnection(name?: string): ConnectionData {\r\n return {\r\n _id: makeId(),\r\n name: name || \"client\",\r\n serverType: \"client\",\r\n udpPort: 4446,\r\n secretKey: \"\",\r\n stretchAsciiKey: false,\r\n useMsgpack: false,\r\n usePathDictionary: false,\r\n enableNotifications: false,\r\n protocolVersion: 1,\r\n udpAddress: \"127.0.0.1\",\r\n helloMessageSender: 60,\r\n testAddress: \"127.0.0.1\",\r\n testPort: 80,\r\n pingIntervalTime: 1\r\n };\r\n}\r\n\r\nfunction defaultServerConnection(name?: string): ConnectionData {\r\n return {\r\n _id: makeId(),\r\n name: name || \"server\",\r\n serverType: \"server\",\r\n udpPort: 4446,\r\n secretKey: \"\",\r\n stretchAsciiKey: false,\r\n useMsgpack: false,\r\n usePathDictionary: false,\r\n protocolVersion: 1\r\n };\r\n}\r\n\r\n/** Attach a stable _id to loaded connections that don't already have one. */\r\nfunction withId(conn: Omit<ConnectionData, \"_id\"> & { _id?: string }): ConnectionData {\r\n return conn._id ? (conn as ConnectionData) : { ...conn, _id: makeId() };\r\n}\r\n\r\n// Fill schema defaults into loaded form data so RJSF has nothing to augment on\r\n// mount — otherwise RJSF fires a synthetic onChange for every field that is\r\n// defined in the schema but absent from the persisted config (e.g.\r\n// stretchAsciiKey on pre-existing connections), which would trip the dirty flag\r\n// and surface \"Unsaved changes\" immediately after a fresh load.\r\nfunction withSchemaDefaults(conn: ConnectionData): ConnectionData {\r\n const isClient = conn.serverType !== \"server\";\r\n const schema = buildWebappConnectionSchema(isClient, conn.protocolVersion) as RJSFSchema;\r\n const { _id, ...formData } = conn;\r\n const enriched = getDefaultFormState(validator, schema, formData) as Record<string, unknown>;\r\n return { ...(enriched as Omit<ConnectionData, \"_id\">), _id };\r\n}\r\n\r\n// Deep equality that is insensitive to key insertion order (unlike\r\n// JSON.stringify). Used to decide whether an RJSF onChange carries a real\r\n// field-level difference.\r\nfunction stableStringify(value: unknown): string {\r\n if (value === null || typeof value !== \"object\") { return JSON.stringify(value); }\r\n if (Array.isArray(value)) {\r\n return \"[\" + value.map(stableStringify).join(\",\") + \"]\";\r\n }\r\n const obj = value as Record<string, unknown>;\r\n const keys = Object.keys(obj).sort();\r\n return \"{\" + keys.map((k) => JSON.stringify(k) + \":\" + stableStringify(obj[k])).join(\",\") + \"}\";\r\n}\r\n\r\nfunction connectionsEqual(a: Record<string, unknown>, b: Record<string, unknown>): boolean {\r\n const aKeys = Object.keys(a);\r\n const bKeys = Object.keys(b);\r\n if (aKeys.length !== bKeys.length) { return false; }\r\n for (const k of aKeys) {\r\n if (!Object.prototype.hasOwnProperty.call(b, k)) { return false; }\r\n const av = a[k];\r\n const bv = b[k];\r\n if (av === bv) { continue; }\r\n if (av !== null && bv !== null && typeof av === \"object\" && typeof bv === \"object\") {\r\n if (stableStringify(av) !== stableStringify(bv)) { return false; }\r\n continue;\r\n }\r\n return false;\r\n }\r\n return true;\r\n}\r\n\r\n// ── Schema ────────────────────────────────────────────────────────────────────\r\n// Single source of truth for field definitions: src/shared/connection-schema.ts\r\n// (also consumed by plugin.schema in src/index.ts).\r\n\r\nconst uiSchemaClient: UiSchema = {\r\n \"ui:order\": [\r\n \"name\", \"serverType\", \"udpAddress\", \"udpPort\", \"secretKey\", \"stretchAsciiKey\", \"protocolVersion\",\r\n \"useMsgpack\", \"usePathDictionary\", \"testAddress\", \"testPort\", \"pingIntervalTime\",\r\n \"helloMessageSender\", \"reliability\", \"congestionControl\", \"bonding\", \"enableNotifications\", \"alertThresholds\"\r\n ],\r\n secretKey: { \"ui:widget\": \"password\", \"ui:help\": \"Use 32-character ASCII, 64-character hex, or 44-character base64\" },\r\n stretchAsciiKey: { \"ui:help\": \"Only applies to 32-char ASCII keys. Must match on both peers.\" },\r\n serverType: { \"ui:widget\": \"select\" },\r\n reliability: {\r\n \"ui:classNames\": \"skel-optional-group\"\r\n },\r\n congestionControl: {\r\n \"ui:classNames\": \"skel-optional-group\"\r\n },\r\n bonding: {\r\n \"ui:classNames\": \"skel-optional-group\"\r\n },\r\n alertThresholds: {\r\n \"ui:classNames\": \"skel-optional-group\"\r\n }\r\n};\r\n\r\nconst uiSchemaServer: UiSchema = {\r\n \"ui:order\": [\r\n \"name\", \"serverType\", \"udpPort\", \"secretKey\", \"stretchAsciiKey\", \"useMsgpack\", \"usePathDictionary\",\r\n \"protocolVersion\", \"reliability\"\r\n ],\r\n secretKey: { \"ui:widget\": \"password\", \"ui:help\": \"Use 32-character ASCII, 64-character hex, or 44-character base64\" },\r\n stretchAsciiKey: { \"ui:help\": \"Only applies to 32-char ASCII keys. Must match on both peers.\" },\r\n serverType: { \"ui:widget\": \"select\" }\r\n};\r\n\r\n// Shared fields preserved when the user toggles server <-> client mode\r\nconst SHARED_FIELDS = [\"name\", \"udpPort\", \"secretKey\", \"stretchAsciiKey\", \"useMsgpack\", \"usePathDictionary\", \"protocolVersion\"];\r\n\r\n// ── Styles ────────────────────────────────────────────────────────────────────\r\n// Using `skel-` prefix (Signal K Edge Link) to avoid collisions with other\r\n// plugins that may inject CSS into the same admin panel page.\r\n\r\nconst css = `\r\n.skel-config { font-family: inherit; }\r\n.skel-dirty-banner {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 8px 14px;\r\n background: #fff3cd;\r\n color: #664d03;\r\n border: 1px solid #ffe69c;\r\n border-radius: 4px;\r\n margin-bottom: 12px;\r\n font-size: 0.88rem;\r\n}\r\n.skel-card {\r\n border: 1px solid #dee2e6;\r\n border-radius: 6px;\r\n margin-bottom: 12px;\r\n overflow: hidden;\r\n}\r\n.skel-card-header {\r\n display: flex;\r\n align-items: center;\r\n padding: 10px 14px;\r\n background: #f8f9fa;\r\n cursor: pointer;\r\n user-select: none;\r\n gap: 10px;\r\n}\r\n.skel-card-header:hover { background: #e9ecef; }\r\n.skel-badge {\r\n display: inline-block;\r\n padding: 2px 8px;\r\n border-radius: 12px;\r\n font-size: 0.75rem;\r\n font-weight: 600;\r\n text-transform: uppercase;\r\n letter-spacing: 0.03em;\r\n}\r\n.skel-badge-server { background: #cfe2ff; color: #084298; }\r\n.skel-badge-client { background: #d1e7dd; color: #0a3622; }\r\n.skel-card-title { font-weight: 600; flex: 1; }\r\n.skel-expand-icon { font-size: 0.8rem; color: #6c757d; }\r\n.skel-btn-remove {\r\n background: none;\r\n border: 1px solid #dc3545;\r\n color: #dc3545;\r\n border-radius: 4px;\r\n padding: 2px 8px;\r\n font-size: 0.8rem;\r\n cursor: pointer;\r\n}\r\n.skel-btn-remove:hover { background: #dc3545; color: white; }\r\n.skel-btn-remove:disabled { opacity: 0.4; cursor: default; border-color: #aaa; color: #aaa; }\r\n.skel-btn-remove:disabled:hover { background: none; }\r\n.skel-card-body { padding: 16px; border-top: 1px solid #dee2e6; }\r\n.skel-toolbar {\r\n display: flex;\r\n gap: 10px;\r\n align-items: center;\r\n margin-top: 16px;\r\n padding-top: 16px;\r\n border-top: 1px solid #dee2e6;\r\n flex-wrap: wrap;\r\n}\r\n.skel-btn {\r\n padding: 7px 16px;\r\n border-radius: 4px;\r\n font-size: 0.95rem;\r\n cursor: pointer;\r\n border: none;\r\n}\r\n.skel-btn-primary { background: #0d6efd; color: white; }\r\n.skel-btn-primary:hover { background: #0b5ed7; }\r\n.skel-btn-primary:disabled { background: #6c757d; cursor: default; }\r\n.skel-btn-secondary { background: white; color: #0d6efd; border: 1px solid #0d6efd; }\r\n.skel-btn-secondary:hover { background: #e7f0ff; }\r\n.skel-alert {\r\n padding: 10px 14px;\r\n border-radius: 4px;\r\n margin-bottom: 14px;\r\n font-size: 0.9rem;\r\n}\r\n.skel-alert-success { background: #d1e7dd; color: #0a3622; border: 1px solid #a3cfbb; }\r\n.skel-alert-error { background: #f8d7da; color: #58151c; border: 1px solid #f1aeb5; }\r\n.skel-alert-saving { background: #fff3cd; color: #664d03; border: 1px solid #ffe69c; }\r\n.skel-dup-warn { font-size: 0.8rem; color: #dc3545; margin-top: 4px; }\r\n.skel-plugin-settings {\r\n border: 1px solid #dee2e6;\r\n border-radius: 6px;\r\n margin-bottom: 20px;\r\n padding: 16px;\r\n background: #f8f9fa;\r\n}\r\n.skel-plugin-settings h3 {\r\n margin: 0 0 12px;\r\n font-size: 1rem;\r\n font-weight: 600;\r\n}\r\n.skel-field-group {\r\n margin-bottom: 14px;\r\n}\r\n.skel-field-group label {\r\n display: block;\r\n font-weight: 500;\r\n margin-bottom: 4px;\r\n font-size: 0.9rem;\r\n}\r\n.skel-field-group input[type=\"text\"],\r\n.skel-field-group input[type=\"password\"] {\r\n width: 100%;\r\n max-width: 420px;\r\n padding: 6px 10px;\r\n border: 1px solid #ced4da;\r\n border-radius: 4px;\r\n font-size: 0.9rem;\r\n}\r\n.skel-field-group input[type=\"checkbox\"] {\r\n margin-right: 6px;\r\n}\r\n.skel-field-desc {\r\n font-size: 0.8rem;\r\n color: #5c6773;\r\n margin-top: 3px;\r\n}\r\n.skel-config .field-description {\r\n color: #5c6773;\r\n font-size: 0.83rem;\r\n line-height: 1.35;\r\n}\r\n.skel-config legend,\r\n.skel-config label {\r\n line-height: 1.2;\r\n overflow-wrap: anywhere;\r\n}\r\n.skel-optional-group {\r\n margin-top: 12px;\r\n border: 1px dashed #ccd5df;\r\n border-radius: 6px;\r\n padding: 10px 12px 4px;\r\n background: #fbfcfe;\r\n}\r\n.skel-optional-group legend {\r\n font-size: 0.92rem;\r\n margin-bottom: 6px;\r\n}\r\n.skel-optional-group .form-group {\r\n margin-bottom: 10px;\r\n}\r\n.skel-optional-group .form-control {\r\n max-width: 340px;\r\n}\r\n`;\r\n\r\n// ── ConnectionCard ────────────────────────────────────────────────────────────\r\n\r\ninterface ConnectionCardProps {\r\n conn: ConnectionData;\r\n index: number;\r\n totalCount: number;\r\n expanded: boolean;\r\n onToggle: () => void;\r\n onChange: (data: ConnectionData) => void;\r\n onRemove: () => void;\r\n}\r\n\r\nfunction ConnectionCard({ conn, index, totalCount, expanded, onToggle, onChange, onRemove }: ConnectionCardProps) {\r\n const isClient = conn.serverType !== \"server\";\r\n const schema = buildWebappConnectionSchema(isClient, conn.protocolVersion) as RJSFSchema;\r\n const uiSchema = isClient ? uiSchemaClient : uiSchemaServer;\r\n const modeLabel = isClient ? \"Client\" : \"Server\";\r\n const displayName = (conn.name || `Connection ${index + 1}`).trim();\r\n\r\n function handleFormChange(e: any) {\r\n const next: ConnectionData = e.formData;\r\n if (next.serverType !== conn.serverType) {\r\n const base = next.serverType === \"server\"\r\n ? defaultServerConnection(next.name)\r\n : defaultClientConnection(next.name);\r\n const merged: ConnectionData = { ...base, _id: conn._id };\r\n for (const k of SHARED_FIELDS) {\r\n if (next[k] !== undefined) { (merged as Record<string, unknown>)[k] = next[k]; }\r\n }\r\n merged.serverType = next.serverType;\r\n onChange(merged);\r\n return;\r\n }\r\n // Skip propagation when the incoming form data is identical to the current\r\n // connection — RJSF can fire onChange with no effective diff (e.g. after\r\n // internal re-renders), and we do not want that to trip the dirty flag.\r\n // Order-insensitive compare so a reshuffled-but-equivalent formData does\r\n // not look like a real edit.\r\n const proposed: ConnectionData = { ...next, _id: conn._id };\r\n const { _id: _aId, ...a } = proposed;\r\n const { _id: _bId, ...b } = conn;\r\n if (connectionsEqual(a, b)) { return; }\r\n onChange(proposed);\r\n }\r\n\r\n // Strip the frontend-only _id before passing to RJSF\r\n const { _id, ...formData } = conn;\r\n\r\n return (\r\n <div className=\"skel-card\">\r\n <div className=\"skel-card-header\" onClick={onToggle} role=\"button\" aria-expanded={expanded}>\r\n <span className={`skel-badge ${isClient ? \"skel-badge-client\" : \"skel-badge-server\"}`}>\r\n {modeLabel}\r\n </span>\r\n <span className=\"skel-card-title\">{displayName}</span>\r\n <span className=\"skel-expand-icon\">{expanded ? \"\\u25B2\" : \"\\u25BC\"}</span>\r\n <button\r\n className=\"skel-btn-remove\"\r\n disabled={totalCount <= 1}\r\n onClick={(e) => { e.stopPropagation(); onRemove(); }}\r\n title={totalCount <= 1 ? \"Cannot remove the only connection\" : \"Remove this connection\"}\r\n >\r\n Remove\r\n </button>\r\n </div>\r\n {expanded && (\r\n <div className=\"skel-card-body\">\r\n <Form\r\n schema={schema}\r\n uiSchema={uiSchema}\r\n formData={formData}\r\n validator={validator}\r\n onChange={handleFormChange}\r\n onSubmit={() => {}}\r\n liveValidate={false}\r\n >\r\n {/* Hide the default submit button – saving is done from the outer toolbar */}\r\n <div />\r\n </Form>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n\r\n// ── Main panel ────────────────────────────────────────────────────────────────\r\n\r\nfunction PluginConfigurationPanel(_props: Record<string, unknown>) {\r\n const [connections, setConnections] = useState<ConnectionData[]>([]);\r\n const [managementApiToken, setManagementApiToken] = useState<string>(\"\");\r\n const [requireManagementApiToken, setRequireManagementApiToken] = useState<boolean>(false);\r\n const [loading, setLoading] = useState(true);\r\n const [loadError, setLoadError] = useState<string | null>(null);\r\n const [saveStatus, setSaveStatus] = useState<SaveStatus | null>(null);\r\n const [inlineValidationMessage, setInlineValidationMessage] = useState<string | null>(null);\r\n const [expandedIndex, setExpandedIndex] = useState<number | null>(0);\r\n const [isDirty, setIsDirty] = useState(false);\r\n const savingRef = useRef(false);\r\n\r\n // ── Load config ─────────────────────────────────────────────────────────────\r\n useEffect(() => {\r\n async function load() {\r\n try {\r\n const res = await apiFetch(`${API_BASE}/plugin-config`);\r\n if (res.status === 401) {\r\n throw new Error(MANAGEMENT_TOKEN_ERROR_MESSAGE);\r\n }\r\n if (!res.ok) { throw new Error(`HTTP ${res.status}: ${res.statusText}`); }\r\n const body = await res.json();\r\n if (!body.success) { throw new Error(body.error || \"Failed to load configuration\"); }\r\n\r\n const cfg = body.configuration || {};\r\n let list: ConnectionData[];\r\n if (Array.isArray(cfg.connections) && cfg.connections.length > 0) {\r\n list = cfg.connections.map((c: Omit<ConnectionData, \"_id\">) => withSchemaDefaults(withId(c)));\r\n } else if (cfg.serverType) {\r\n list = [withSchemaDefaults(withId(cfg))];\r\n } else {\r\n list = [defaultClientConnection()];\r\n }\r\n setConnections(list);\r\n setManagementApiToken(typeof cfg.managementApiToken === \"string\" ? cfg.managementApiToken : \"\");\r\n setRequireManagementApiToken(cfg.requireManagementApiToken === true);\r\n setExpandedIndex(0);\r\n setIsDirty(false);\r\n } catch (err: unknown) {\r\n setLoadError(err instanceof Error ? err.message : String(err));\r\n } finally {\r\n setLoading(false);\r\n }\r\n }\r\n load();\r\n }, []);\r\n\r\n // ── Duplicate server-port detection ─────────────────────────────────────────\r\n const serverPorts = connections\r\n .filter((c) => c.serverType === \"server\")\r\n .map((c) => c.udpPort);\r\n const duplicatePortSet = new Set(\r\n serverPorts.filter((p, i) => serverPorts.indexOf(p) !== i)\r\n );\r\n\r\n // ── Handlers ─────────────────────────────────────────────────────────────────\r\n function markDirty() {\r\n setIsDirty(true);\r\n setSaveStatus(null);\r\n setInlineValidationMessage(null);\r\n }\r\n\r\n function updateConnection(idx: number, data: ConnectionData) {\r\n setConnections((prev) => prev.map((c, i) => (i === idx ? data : c)));\r\n markDirty();\r\n }\r\n\r\n function addServer() {\r\n setConnections((prev) => {\r\n const next = [...prev, defaultServerConnection(`server-${prev.length + 1}`)];\r\n setExpandedIndex(next.length - 1);\r\n return next;\r\n });\r\n markDirty();\r\n }\r\n\r\n function addClient() {\r\n setConnections((prev) => {\r\n const next = [...prev, defaultClientConnection(`client-${prev.length + 1}`)];\r\n setExpandedIndex(next.length - 1);\r\n return next;\r\n });\r\n markDirty();\r\n }\r\n\r\n function removeConnection(idx: number) {\r\n setConnections((prev) => {\r\n if (prev.length <= 1) return prev;\r\n const next = prev.filter((_, i) => i !== idx);\r\n setExpandedIndex((prevExpanded) => (prevExpanded !== null && prevExpanded >= idx && prevExpanded > 0 ? prevExpanded - 1 : prevExpanded));\r\n return next;\r\n });\r\n markDirty();\r\n }\r\n\r\n function toggleExpand(idx: number) {\r\n setExpandedIndex((prev) => (prev === idx ? null : idx));\r\n }\r\n\r\n const handleSave = useCallback(async () => {\r\n if (savingRef.current) { return; }\r\n if (connections.length === 0) {\r\n setInlineValidationMessage(\"At least one connection is required before saving.\");\r\n setSaveStatus({\r\n type: \"error\",\r\n message: \"Cannot save an empty configuration. Add at least one connection.\"\r\n });\r\n return;\r\n }\r\n\r\n setInlineValidationMessage(null);\r\n if (duplicatePortSet.size > 0) {\r\n setSaveStatus({\r\n type: \"error\",\r\n message: `Duplicate server ports detected: ${[...duplicatePortSet].join(\", \")}. Each server must use a unique UDP port.`\r\n });\r\n return;\r\n }\r\n\r\n savingRef.current = true;\r\n setSaveStatus({ type: \"saving\", message: \"Saving configuration...\" });\r\n try {\r\n const payload = connections.map(({ _id, ...rest }) => rest);\r\n const res = await apiFetch(`${API_BASE}/plugin-config`, {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: JSON.stringify({\r\n connections: payload,\r\n managementApiToken: managementApiToken,\r\n requireManagementApiToken: requireManagementApiToken\r\n })\r\n });\r\n if (res.status === 401) {\r\n throw new Error(MANAGEMENT_TOKEN_ERROR_MESSAGE);\r\n }\r\n const body = await res.json();\r\n if (res.ok && body.success) {\r\n setSaveStatus({ type: \"success\", message: body.message || \"Configuration saved. Plugin restarting...\" });\r\n setIsDirty(false);\r\n } else {\r\n throw new Error(body.error || \"Failed to save\");\r\n }\r\n } catch (err: unknown) {\r\n setSaveStatus({ type: \"error\", message: err instanceof Error ? err.message : String(err) });\r\n } finally {\r\n savingRef.current = false;\r\n }\r\n }, [connections, duplicatePortSet, managementApiToken, requireManagementApiToken]);\r\n\r\n // ── Render ────────────────────────────────────────────────────────────────────\r\n if (loading) {\r\n return <div style={{ padding: \"20px\", textAlign: \"center\" }}>Loading configuration...</div>;\r\n }\r\n\r\n if (loadError) {\r\n return (\r\n <div style={{ padding: \"20px\" }}>\r\n <div className=\"skel-alert skel-alert-error\">\r\n <strong>Error loading configuration:</strong> {loadError}\r\n </div>\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <div className=\"skel-config\">\r\n <style>{css}</style>\r\n\r\n {isDirty && saveStatus?.type !== \"saving\" && (\r\n <div className=\"skel-dirty-banner\">\r\n <span>&#9888;</span>\r\n <span>You have unsaved changes.</span>\r\n </div>\r\n )}\r\n\r\n {saveStatus && (\r\n <div className={`skel-alert skel-alert-${saveStatus.type === \"saving\" ? \"saving\" : saveStatus.type === \"success\" ? \"success\" : \"error\"}`}>\r\n {saveStatus.message}\r\n </div>\r\n )}\r\n\r\n {/* Plugin-level security settings */}\r\n <div className=\"skel-plugin-settings\">\r\n <h3>Plugin Security Settings</h3>\r\n <div className=\"skel-field-group\">\r\n <label htmlFor=\"skel-mgmt-token\">Management API Token</label>\r\n <input\r\n id=\"skel-mgmt-token\"\r\n type=\"password\"\r\n value={managementApiToken}\r\n placeholder=\"Leave empty for open access\"\r\n onChange={(e) => { setManagementApiToken(e.target.value); markDirty(); }}\r\n autoComplete=\"new-password\"\r\n />\r\n <div className=\"skel-field-desc\">\r\n Shared secret to protect the management API endpoints. Strongly recommended for\r\n production. Can also be set via the{\" \"}\r\n <code>SIGNALK_EDGE_LINK_MANAGEMENT_TOKEN</code> environment variable (env var takes\r\n priority). Leave empty to allow open access.\r\n </div>\r\n </div>\r\n <div className=\"skel-field-group\">\r\n <label>\r\n <input\r\n type=\"checkbox\"\r\n checked={requireManagementApiToken}\r\n onChange={(e) => { setRequireManagementApiToken(e.target.checked); markDirty(); }}\r\n />\r\n Require Management API Token\r\n </label>\r\n <div className=\"skel-field-desc\">\r\n When enabled, all management API requests are rejected if no token is configured\r\n (fail-closed). When disabled, requests are allowed if no token is set (open access).\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {connections.map((conn, idx) => (\r\n <div key={conn._id}>\r\n <ConnectionCard\r\n conn={conn}\r\n index={idx}\r\n totalCount={connections.length}\r\n expanded={expandedIndex === idx}\r\n onToggle={() => toggleExpand(idx)}\r\n onChange={(data: ConnectionData) => updateConnection(idx, data)}\r\n onRemove={() => removeConnection(idx)}\r\n />\r\n {conn.serverType === \"server\" && duplicatePortSet.has(conn.udpPort) && (\r\n <div className=\"skel-dup-warn\">\r\n Port {conn.udpPort} is used by multiple server connections. Each server requires a unique port.\r\n </div>\r\n )}\r\n </div>\r\n ))}\r\n\r\n <div className=\"skel-toolbar\">\r\n <button className=\"skel-btn skel-btn-secondary\" onClick={addServer}>\r\n + Add Server\r\n </button>\r\n <button className=\"skel-btn skel-btn-secondary\" onClick={addClient}>\r\n + Add Client\r\n </button>\r\n <button\r\n className=\"skel-btn skel-btn-primary\"\r\n onClick={handleSave}\r\n disabled={(saveStatus && saveStatus.type === \"saving\") || connections.length === 0}\r\n >\r\n {isDirty ? \"Save Changes\" : \"Save Configuration\"}\r\n </button>\r\n {inlineValidationMessage && (\r\n <span style={{ color: \"#dc3545\", fontSize: \"0.85rem\", fontWeight: 500 }}>\r\n {inlineValidationMessage}\r\n </span>\r\n )}\r\n <span style={{ fontSize: \"0.85rem\", color: \"#6c757d\" }}>\r\n {connections.length} connection{connections.length !== 1 ? \"s\" : \"\"}\r\n {\" \\u00B7 \"}\r\n {connections.filter((c) => c.serverType === \"server\").length} server\r\n {connections.filter((c) => c.serverType === \"server\").length !== 1 ? \"s\" : \"\"}\r\n {\", \"}\r\n {connections.filter((c) => c.serverType !== \"server\").length} client\r\n {connections.filter((c) => c.serverType !== \"server\").length !== 1 ? \"s\" : \"\"}\r\n </span>\r\n </div>\r\n </div>\r\n );\r\n}\r\n\r\nexport default PluginConfigurationPanel;\r\n"],"names":["MANAGEMENT_TOKEN_ERROR_MESSAGE","DEFAULT_AUTH_CONFIG","token","localStorageKey","queryParam","includeTokenInQuery","headerMode","apiFetch","input","init","config","window","runtime","__EDGE_LINK_AUTH__","readRuntimeAuthConfig","String","trim","tokenFromQuery","URLSearchParams","location","search","get","localStorage","tokenFromStorage","getItem","resolveToken","headers","Headers","normalizedMode","toLowerCase","set","attachAuthHeaders","fetch","commonConnectionProperties","name","type","title","description","default","maxLength","serverType","oneOf","const","udpPort","minimum","maximum","secretKey","minLength","pattern","stretchAsciiKey","toLocaleString","useMsgpack","usePathDictionary","protocolVersion","clientTransportProperties","udpAddress","helloMessageSender","testAddress","testPort","pingIntervalTime","heartbeatInterval","clientReliabilityProperty","properties","retransmitQueueSize","maxRetransmits","retransmitMaxAge","retransmitMinAge","retransmitRttMultiplier","ackIdleDrainAge","forceDrainAfterAckIdle","forceDrainAfterMs","recoveryBurstEnabled","recoveryBurstSize","recoveryBurstIntervalMs","recoveryAckGapMs","serverReliabilityProperty","ackInterval","ackResendInterval","nakTimeout","congestionControlProperty","enabled","targetRTT","nominalDeltaTimer","minDeltaTimer","maxDeltaTimer","bondingProperty","mode","primary","address","port","interface","backup","failover","rttThreshold","lossThreshold","healthCheckInterval","failbackDelay","heartbeatTimeout","enableNotificationsProperty","alertThresholdsProperty","rtt","warning","critical","packetLoss","retransmitRate","jitter","queueDepth","buildWebappConnectionSchema","isClient","isReliableProtocol","Number","props","required","Object","assign","enableNotifications","push","reliability","congestionControl","bonding","alertThresholds","API_BASE","_idSeq","makeId","Date","now","defaultClientConnection","_id","defaultServerConnection","withId","conn","withSchemaDefaults","schema","formData","stableStringify","value","JSON","stringify","Array","isArray","map","join","obj","keys","sort","k","uiSchemaClient","uiSchemaServer","SHARED_FIELDS","ConnectionCard","index","totalCount","expanded","onToggle","onChange","onRemove","uiSchema","modeLabel","displayName","className","onClick","role","disabled","e","stopPropagation","validator","next","merged","undefined","proposed","_aId","a","_bId","b","aKeys","bKeys","length","prototype","hasOwnProperty","call","av","bv","connectionsEqual","onSubmit","liveValidate","_props","connections","setConnections","useState","managementApiToken","setManagementApiToken","requireManagementApiToken","setRequireManagementApiToken","loading","setLoading","loadError","setLoadError","saveStatus","setSaveStatus","inlineValidationMessage","setInlineValidationMessage","expandedIndex","setExpandedIndex","isDirty","setIsDirty","savingRef","useRef","useEffect","async","res","status","Error","ok","statusText","body","json","success","error","cfg","configuration","list","c","err","message","load","serverPorts","filter","duplicatePortSet","Set","p","i","indexOf","markDirty","handleSave","useCallback","current","size","payload","rest","method","style","padding","textAlign","htmlFor","id","placeholder","target","autoComplete","checked","idx","key","prev","toggleExpand","data","updateConnection","_","prevExpanded","removeConnection","has","color","fontSize","fontWeight"],"sourceRoot":""}