signalk-to-noforeignland 0.1.24 → 0.1.25
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/CHANGELOG.md +21 -1
- package/README.md +11 -8
- package/index.js +289 -143
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
|
+
0.1.25
|
|
2
|
+
* CHANGE: Minimum boat move default increased from 50m to 80m
|
|
3
|
+
* CHANGE: Updated the README.md
|
|
4
|
+
* CHANGE: Use public ipv4 DNS instead of local with cache for testInternet()
|
|
5
|
+
* Final version after successful testing SV MOIN and SV KIAPA NUI
|
|
6
|
+
|
|
7
|
+
0.1.25-beta.3
|
|
8
|
+
* NEW: Check if boat key is set on startup, else report error to dashboard
|
|
9
|
+
* CHANGE: Changing the order and label of the Plugin Settings to make it more clear for unexpierienced users and grouped to Mandatory, Advanced and Expert.
|
|
10
|
+
* CHANGE: Migration of < 0.1.25 Plugin settings to new structure.
|
|
11
|
+
* CHANGE: PluginStatus last track sent "Never" changed to "Not transfered since plugin start" to avoid confusions.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
0.1.25-beta.2
|
|
15
|
+
* CHANGE: Typo in pluginName fixed
|
|
16
|
+
* CHANGE: Dates for SetPlugin now ISO8601 formated (https://github.com/noforeignland/nfl-signalk/issues/9)
|
|
17
|
+
|
|
18
|
+
0.1.25-beta.1
|
|
19
|
+
* CHANGE: User mattzilla470 reported timout Issues on VE Cerbo with a small CPU and using 4G (https://github.com/noforeignland/nfl-signalk/issues/7). So added a timout option in the plugin config and a tripple retry while increasing the timeout for the API call.
|
|
20
|
+
|
|
1
21
|
0.1.24
|
|
2
|
-
* CHANGE: testInternet only uses ipv4 now, some users don't have ipv6 configured properly and where unable to reach the API, when testInternet returned false
|
|
22
|
+
* CHANGE: testInternet() only uses ipv4 now, some users don't have ipv6 configured properly and where unable to reach the API, when testInternet returned false
|
|
3
23
|
* NEW: PluginStatus on SK dashboard now shows last savePoint and last API transfer, so a user has more feedback what the app is doing without enabling the debug log and crawling though it.
|
|
4
24
|
* CHANGE: Renamed CHANGELOG to CHANGELOG.md
|
|
5
25
|
* CHANGE: CHANGELOG ORDER - newest on top.
|
package/README.md
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
# SignalK To NFL
|
|
2
2
|
Effortlessly log your boat's movement to **noforeignland.com**
|
|
3
3
|
|
|
4
|
-
## Testing Notes
|
|
5
|
-
- This version includes experimental features
|
|
6
|
-
- Report issues on GitHub
|
|
7
|
-
- Not recommended for production use
|
|
8
|
-
|
|
9
|
-
|
|
10
4
|
## Features
|
|
11
5
|
* Automatically log your position to NFL
|
|
12
6
|
* Send detailed tracks to log your entire trip and not just your final position
|
|
13
7
|
* Can be used in near real time or cache and upload when stopped and data-connection is available.
|
|
8
|
+
* Can sent a 24h keepalive, when off the boat for a while.
|
|
14
9
|
|
|
15
|
-
|
|
10
|
+
## Issues
|
|
11
|
+
* Report issues on GitHub (https://github.com/noforeignland/nfl-signalk/issues)
|
|
16
12
|
|
|
17
13
|
## Requirements
|
|
14
|
+
* An internet connection is required in order to update NFL.
|
|
15
|
+
* A navigation.position data path inside Signal K for self, which is your current GPS position
|
|
18
16
|
* A **noforeignland.com** account
|
|
19
17
|
* Your Boat API Key from the **noforeignland.com** website:
|
|
20
18
|
* Account > Settings > Boat tracking > API Key
|
|
21
19
|
|
|
22
20
|
> Note your Boat API Key is not available in the app.
|
|
23
|
-
> You must sign in to the **noforeignland.com** website (using the same authentication method you use for the app: Google. Facebook, Email).
|
|
21
|
+
> You must sign in to the **noforeignland.com** website (using the same authentication method you use for the app: Google. Facebook, Email).
|
|
22
|
+
|
|
23
|
+
## Configuration
|
|
24
|
+
1. Add your boat's API Key into the Server > Plugin Config > Signal K to Noforeignland > Boat API Key
|
|
25
|
+
2. Hit "Submit"
|
|
26
|
+
3. Restart the Signal K server
|
package/index.js
CHANGED
|
@@ -17,7 +17,7 @@ class SignalkToNoforeignland {
|
|
|
17
17
|
constructor(app) {
|
|
18
18
|
this.app = app;
|
|
19
19
|
this.pluginId = 'signalk-to-noforeignland';
|
|
20
|
-
this.pluginName = '
|
|
20
|
+
this.pluginName = 'Signal K to Noforeignland';
|
|
21
21
|
this.creator = 'signalk-track-logger';
|
|
22
22
|
|
|
23
23
|
// runtime state
|
|
@@ -30,79 +30,110 @@ class SignalkToNoforeignland {
|
|
|
30
30
|
this.lastSuccessfulTransfer = null;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
getSchema() {
|
|
34
|
+
return {
|
|
35
|
+
title: this.pluginName,
|
|
36
|
+
description: 'Some parameters need for use',
|
|
37
|
+
type: 'object',
|
|
38
|
+
required: ['boatApiKey', 'apiCron'],
|
|
39
|
+
properties: {
|
|
40
|
+
// Mandatory Settings Group
|
|
41
|
+
mandatory: {
|
|
42
|
+
type: 'object',
|
|
43
|
+
title: 'Mandatory Settings',
|
|
44
|
+
properties: {
|
|
45
|
+
boatApiKey: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
title: 'Boat API Key',
|
|
48
|
+
description: 'Boat API Key from noforeignland.com. Can be found in Account > Settings > Boat tracking > API Key.'
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
// Advanced Settings Group
|
|
54
|
+
advanced: {
|
|
55
|
+
type: 'object',
|
|
56
|
+
title: 'Advanced Settings',
|
|
57
|
+
properties: {
|
|
58
|
+
minMove: {
|
|
59
|
+
type: 'number',
|
|
60
|
+
title: 'Minimum boat move to log in meters',
|
|
61
|
+
description: 'To keep file sizes small we only log positions if a move larger than this size (if set to 0 will log every move)',
|
|
62
|
+
default: 80
|
|
63
|
+
},
|
|
64
|
+
minSpeed: {
|
|
65
|
+
type: 'number',
|
|
66
|
+
title: 'Minimum boat speed to log in knots',
|
|
67
|
+
description: 'To keep file sizes small we only log positions if boat speed goes above this value to minimize recording position on anchor or mooring (if set to 0 will log every move)',
|
|
68
|
+
default: 1.5
|
|
69
|
+
},
|
|
70
|
+
sendWhileMoving: {
|
|
71
|
+
type: 'boolean',
|
|
72
|
+
title: 'Attempt sending location while moving',
|
|
73
|
+
description: 'Should the plugin attempt to send tracking data to NFL while detecting the vessel is moving or only when stopped?',
|
|
74
|
+
default: true
|
|
75
|
+
},
|
|
76
|
+
ping_api_every_24h: {
|
|
77
|
+
type: 'boolean',
|
|
78
|
+
title: 'Force a send every 24 hours',
|
|
79
|
+
description: 'Keeps your boat active on NFL in your current location even if you do not move',
|
|
80
|
+
default: true
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
// Expert Settings Group
|
|
86
|
+
expert: {
|
|
87
|
+
type: 'object',
|
|
88
|
+
title: 'Expert Settings',
|
|
89
|
+
properties: {
|
|
90
|
+
filterSource: {
|
|
91
|
+
type: 'string',
|
|
92
|
+
title: 'Position source device',
|
|
93
|
+
description: 'EMPTY DEFAULT IS FINE - Set this value to the name of a source if you want to only use the position given by that source.'
|
|
94
|
+
},
|
|
95
|
+
trackDir: {
|
|
96
|
+
type: 'string',
|
|
97
|
+
title: 'Directory to cache tracks',
|
|
98
|
+
description: 'EMPTY DEFAULT IS FINE - Path in server filesystem, absolute or from plugin directory.\noptional param (only used to keep file cache).'
|
|
99
|
+
},
|
|
100
|
+
keepFiles: {
|
|
101
|
+
type: 'boolean',
|
|
102
|
+
title: 'Keep track files on disk',
|
|
103
|
+
description: 'If you have a lot of hard drive space you can keep the track files for logging purposes.',
|
|
104
|
+
default: false
|
|
105
|
+
},
|
|
106
|
+
trackFrequency: {
|
|
107
|
+
type: 'integer',
|
|
108
|
+
title: 'Position tracking frequency in seconds',
|
|
109
|
+
description: 'To keep file sizes small we only log positions once in a while (unless you set this value to 0)',
|
|
110
|
+
default: 60
|
|
111
|
+
},
|
|
112
|
+
apiCron: {
|
|
113
|
+
type: 'string',
|
|
114
|
+
title: 'Send attempt CRON',
|
|
115
|
+
description: 'We send the tracking data to NFL once in a while, you can set the schedule with this setting.\nCRON format: https://crontab.guru/',
|
|
116
|
+
default: '*/10 * * * *'
|
|
117
|
+
},
|
|
118
|
+
internetTestTimeout: {
|
|
119
|
+
type: 'number',
|
|
120
|
+
title: 'Timeout for testing internet connection in ms',
|
|
121
|
+
description: 'Set this number higher for slower computers and internet connections',
|
|
122
|
+
default: 2000
|
|
123
|
+
},
|
|
124
|
+
apiTimeout: {
|
|
41
125
|
type: 'integer',
|
|
42
|
-
title: '
|
|
43
|
-
description: '
|
|
44
|
-
default:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
title: 'Minimum boat move to log in meters',
|
|
49
|
-
description: 'To keep file sizes small we only log positions if a move larger than this size (if set to 0 will log every move)',
|
|
50
|
-
default: 50
|
|
51
|
-
},
|
|
52
|
-
minSpeed: {
|
|
53
|
-
type: 'number',
|
|
54
|
-
title: 'Minimum boat speed to log in knots',
|
|
55
|
-
description: 'To keep file sizes small we only log positions if boat speed goes above this value to minimize recording position on anchor or mooring (if set to 0 will log every move)',
|
|
56
|
-
default: 1.5
|
|
57
|
-
},
|
|
58
|
-
apiCron: {
|
|
59
|
-
type: 'string',
|
|
60
|
-
title: 'Send attempt CRON',
|
|
61
|
-
description: 'We send the tracking data to NFL once in a while, you can set the schedule with this setting.\nCRON format: https://crontab.guru/',
|
|
62
|
-
default: '*/10 * * * *'
|
|
63
|
-
},
|
|
64
|
-
boatApiKey: {
|
|
65
|
-
type: 'string',
|
|
66
|
-
title: 'Boat API key',
|
|
67
|
-
description: 'Boat API key from noforeignland.com. Can be found in Account > Settings > Boat tracking > API Key.\n*required only in API method is set*'
|
|
68
|
-
},
|
|
69
|
-
internetTestTimeout: {
|
|
70
|
-
type: 'number',
|
|
71
|
-
title: 'Timeout for testing internet connection in ms',
|
|
72
|
-
description: 'Set this number higher for slower computers and internet connections',
|
|
73
|
-
default: 2000
|
|
74
|
-
},
|
|
75
|
-
sendWhileMoving: {
|
|
76
|
-
type: 'boolean',
|
|
77
|
-
title: 'Attempt sending location while moving',
|
|
78
|
-
description: 'Should the plugin attempt to send tracking data to NFL while detecting the vessel is moving or only when stopped?',
|
|
79
|
-
default: true
|
|
80
|
-
},
|
|
81
|
-
filterSource: {
|
|
82
|
-
type: 'string',
|
|
83
|
-
title: 'Position source device',
|
|
84
|
-
description: 'Set this value to the name of a source if you want to only use the position given by that source.'
|
|
85
|
-
},
|
|
86
|
-
trackDir: {
|
|
87
|
-
type: 'string',
|
|
88
|
-
title: 'Directory to cache tracks.',
|
|
89
|
-
description: 'Path in server filesystem, absolute or from plugin directory.\noptional param (only used to keep file cache).'
|
|
90
|
-
},
|
|
91
|
-
keepFiles: {
|
|
92
|
-
type: 'boolean',
|
|
93
|
-
title: 'Should keep track files on disk?',
|
|
94
|
-
description: 'If you have a lot of hard drive space you can keep the track files for logging purposes.',
|
|
95
|
-
default: false
|
|
96
|
-
},
|
|
97
|
-
ping_api_every_24h: {
|
|
98
|
-
type: 'boolean',
|
|
99
|
-
title: 'Should I force a send every 24 hours',
|
|
100
|
-
description: 'Keeps your boat active on NFL in your current location even if you do not move',
|
|
101
|
-
default: true
|
|
126
|
+
title: 'API request timeout in seconds',
|
|
127
|
+
description: 'Timeout for sending data to NFL API. Increase for slow connections.',
|
|
128
|
+
default: 30,
|
|
129
|
+
minimum: 10,
|
|
130
|
+
maximum: 180
|
|
131
|
+
}
|
|
102
132
|
}
|
|
103
133
|
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
106
137
|
|
|
107
138
|
getPluginObject() {
|
|
108
139
|
return {
|
|
@@ -116,38 +147,107 @@ class SignalkToNoforeignland {
|
|
|
116
147
|
}
|
|
117
148
|
|
|
118
149
|
async start(options = {}, restartPlugin) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
150
|
+
|
|
151
|
+
// Backward compatibility: migrate old flat structure to new nested structure
|
|
152
|
+
let needsSave = false;
|
|
153
|
+
if (options.boatApiKey && !options.mandatory) {
|
|
154
|
+
// Old config detected, migrate to new structure
|
|
155
|
+
this.app.debug('Migrating old configuration to new grouped structure');
|
|
156
|
+
needsSave = true;
|
|
157
|
+
|
|
158
|
+
options = {
|
|
159
|
+
mandatory: {
|
|
160
|
+
boatApiKey: options.boatApiKey
|
|
161
|
+
},
|
|
162
|
+
advanced: {
|
|
163
|
+
minMove: options.minMove !== undefined ? options.minMove : 50,
|
|
164
|
+
minSpeed: options.minSpeed !== undefined ? options.minSpeed : 1.5,
|
|
165
|
+
sendWhileMoving: options.sendWhileMoving !== undefined ? options.sendWhileMoving : true,
|
|
166
|
+
ping_api_every_24h: options.ping_api_every_24h !== undefined ? options.ping_api_every_24h : true
|
|
167
|
+
},
|
|
168
|
+
expert: {
|
|
169
|
+
filterSource: options.filterSource,
|
|
170
|
+
trackDir: options.trackDir,
|
|
171
|
+
keepFiles: options.keepFiles !== undefined ? options.keepFiles : false,
|
|
172
|
+
trackFrequency: options.trackFrequency !== undefined ? options.trackFrequency : 60,
|
|
173
|
+
internetTestTimeout: options.internetTestTimeout !== undefined ? options.internetTestTimeout : 2000,
|
|
174
|
+
apiCron: options.apiCron || '*/10 * * * *',
|
|
175
|
+
apiTimeout: options.apiTimeout !== undefined ? options.apiTimeout : 30
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// Save the migrated configuration
|
|
180
|
+
try {
|
|
181
|
+
this.app.debug('Saving migrated configuration...');
|
|
182
|
+
await this.app.savePluginOptions(options, () => {
|
|
183
|
+
this.app.debug('Configuration successfully migrated and saved');
|
|
184
|
+
});
|
|
185
|
+
} catch (err) {
|
|
186
|
+
this.app.debug('Failed to save migrated configuration:', err.message);
|
|
187
|
+
// Continue anyway - the migration will work in memory
|
|
124
188
|
}
|
|
189
|
+
}
|
|
125
190
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
191
|
+
// Flatten the nested structure for easier access and apply defaults
|
|
192
|
+
this.options = {
|
|
193
|
+
// Mandatory defaults
|
|
194
|
+
boatApiKey: options.mandatory?.boatApiKey,
|
|
195
|
+
|
|
196
|
+
// Advanced defaults
|
|
197
|
+
minMove: options.advanced?.minMove !== undefined ? options.advanced.minMove : 80,
|
|
198
|
+
minSpeed: options.advanced?.minSpeed !== undefined ? options.advanced.minSpeed : 1.5,
|
|
199
|
+
sendWhileMoving: options.advanced?.sendWhileMoving !== undefined ? options.advanced.sendWhileMoving : true,
|
|
200
|
+
ping_api_every_24h: options.advanced?.ping_api_every_24h !== undefined ? options.advanced.ping_api_every_24h : true,
|
|
201
|
+
|
|
202
|
+
// Expert defaults
|
|
203
|
+
filterSource: options.expert?.filterSource,
|
|
204
|
+
trackDir: options.expert?.trackDir || defaultTracksDir,
|
|
205
|
+
keepFiles: options.expert?.keepFiles !== undefined ? options.expert.keepFiles : false,
|
|
206
|
+
trackFrequency: options.expert?.trackFrequency !== undefined ? options.expert.trackFrequency : 60,
|
|
207
|
+
internetTestTimeout: options.expert?.internetTestTimeout !== undefined ? options.expert.internetTestTimeout : 2000,
|
|
208
|
+
apiCron: options.expert?.apiCron || '*/10 * * * *',
|
|
209
|
+
apiTimeout: options.expert?.apiTimeout !== undefined ? options.expert.apiTimeout : 30
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// Validate that boatApiKey is set
|
|
213
|
+
if (!this.options.boatApiKey || this.options.boatApiKey.trim() === '') {
|
|
214
|
+
const errorMsg = 'No boat API key configured. Please set your API key in plugin settings (Mandatory Settings > Boat API key). You can find your API key at noforeignland.com under Account > Settings > Boat tracking > API Key.';
|
|
215
|
+
this.app.debug(errorMsg);
|
|
216
|
+
this.app.setPluginError(errorMsg);
|
|
217
|
+
this.stop();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!path.isAbsolute(this.options.trackDir)) {
|
|
222
|
+
this.options.trackDir = path.join(__dirname, this.options.trackDir);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!this.createDir(this.options.trackDir)) {
|
|
226
|
+
this.stop();
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
130
229
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
230
|
+
this.app.debug('track logger started, now logging to', this.options.trackDir);
|
|
231
|
+
this.app.setPluginStatus(`Started${needsSave ? ' (config migrated)' : ''}`);
|
|
232
|
+
this.upSince = new Date().getTime();
|
|
134
233
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
234
|
+
// adjust default CRON if unchanged
|
|
235
|
+
if (!this.options.apiCron || this.options.apiCron === '*/10 * * * *') {
|
|
236
|
+
const startMinute = Math.floor(Math.random() * 10);
|
|
237
|
+
const startSecond = Math.floor(Math.random() * 60);
|
|
238
|
+
this.options.apiCron = `${startSecond} ${startMinute}/10 * * * *`;
|
|
239
|
+
}
|
|
141
240
|
|
|
142
|
-
|
|
241
|
+
this.app.debug('Setting CRON to ', this.options.apiCron);
|
|
242
|
+
this.app.debug('trackFrequency is set to', this.options.trackFrequency, 'seconds');
|
|
143
243
|
|
|
144
|
-
|
|
145
|
-
|
|
244
|
+
// subscribe and logging
|
|
245
|
+
this.doLogging();
|
|
146
246
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
247
|
+
// start cron job
|
|
248
|
+
this.cron = new CronJob(this.options.apiCron, this.interval.bind(this));
|
|
249
|
+
this.cron.start();
|
|
250
|
+
}
|
|
151
251
|
|
|
152
252
|
stop() {
|
|
153
253
|
this.app.debug('plugin stopped');
|
|
@@ -280,11 +380,10 @@ class SignalkToNoforeignland {
|
|
|
280
380
|
this.app.debug(`save data point:`, obj);
|
|
281
381
|
await fs.appendFile(path.join(this.options.trackDir, routeSaveName), JSON.stringify(obj) + EOL);
|
|
282
382
|
|
|
283
|
-
|
|
284
|
-
const
|
|
285
|
-
const lastTransferTime = this.lastSuccessfulTransfer ? this.lastSuccessfulTransfer.toLocaleString() : 'Never';
|
|
383
|
+
const lastSaveTime = new Date().toISOString();
|
|
384
|
+
const lastTransferTime = this.lastSuccessfulTransfer ? this.lastSuccessfulTransfer.toISOString() : 'Not transfered since plugin start';
|
|
286
385
|
this.app.setPluginStatus(`Last save: ${lastSaveTime} | Last transfer: ${lastTransferTime}`);
|
|
287
|
-
|
|
386
|
+
}
|
|
288
387
|
|
|
289
388
|
isValidLatitude(obj) {
|
|
290
389
|
return this.isDefinedNumber(obj) && obj > -90 && obj < 90;
|
|
@@ -367,33 +466,41 @@ class SignalkToNoforeignland {
|
|
|
367
466
|
}
|
|
368
467
|
}
|
|
369
468
|
|
|
370
|
-
|
|
469
|
+
async testInternet() {
|
|
371
470
|
const dns = require('dns').promises;
|
|
372
471
|
|
|
373
472
|
this.app.debug('testing internet connection');
|
|
374
473
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
474
|
+
const timeoutMs = this.options.internetTestTimeout || 2000;
|
|
475
|
+
|
|
476
|
+
// Prüfe mehrere öffentliche DNS-Server
|
|
477
|
+
const dnsServers = [
|
|
478
|
+
{ name: 'Google DNS', ip: '8.8.8.8' },
|
|
479
|
+
{ name: 'Cloudflare DNS', ip: '1.1.1.1' }
|
|
480
|
+
];
|
|
481
|
+
|
|
482
|
+
for (const server of dnsServers) {
|
|
483
|
+
try {
|
|
484
|
+
// Versuche, den DNS-Server direkt zu erreichen
|
|
485
|
+
// Wir machen einen reverse lookup auf die IP selbst
|
|
486
|
+
const result = await Promise.race([
|
|
487
|
+
dns.reverse(server.ip),
|
|
488
|
+
new Promise((_, reject) =>
|
|
489
|
+
setTimeout(() => reject(new Error('DNS timeout')), timeoutMs)
|
|
490
|
+
)
|
|
491
|
+
]);
|
|
492
|
+
|
|
493
|
+
this.app.debug(`internet connection = true, ${server.name} (${server.ip}) is reachable`);
|
|
387
494
|
return true;
|
|
388
|
-
}
|
|
389
|
-
this.app.debug(
|
|
390
|
-
|
|
495
|
+
} catch (err) {
|
|
496
|
+
this.app.debug(`${server.name} (${server.ip}) not reachable:`, err.message);
|
|
497
|
+
// Weiter zum nächsten Server
|
|
391
498
|
}
|
|
392
|
-
} catch (err) {
|
|
393
|
-
this.app.debug('internet connection = false, error:', err.message);
|
|
394
|
-
return false;
|
|
395
499
|
}
|
|
396
|
-
|
|
500
|
+
|
|
501
|
+
this.app.debug('internet connection = false, no public DNS servers reachable');
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
397
504
|
|
|
398
505
|
async checkTrack() {
|
|
399
506
|
const trackFile = path.join(this.options.trackDir, routeSaveName);
|
|
@@ -429,36 +536,75 @@ class SignalkToNoforeignland {
|
|
|
429
536
|
const headers = { 'X-NFL-API-Key': pluginApiKey };
|
|
430
537
|
this.app.debug('sending track to API');
|
|
431
538
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
539
|
+
// Retry-Logik mit exponentiell steigendem Timeout
|
|
540
|
+
const maxRetries = 3;
|
|
541
|
+
const baseTimeout = (this.options.apiTimeout || 30) * 1000; // Konfigurierbarer Basis-Timeout in ms
|
|
542
|
+
|
|
543
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
544
|
+
try {
|
|
545
|
+
const currentTimeout = baseTimeout * attempt; // 30s, 60s, 90s
|
|
546
|
+
this.app.debug(`Attempt ${attempt}/${maxRetries} with ${currentTimeout}ms timeout`);
|
|
547
|
+
|
|
548
|
+
// AbortController für Timeout
|
|
549
|
+
const controller = new AbortController();
|
|
550
|
+
const timeoutId = setTimeout(() => controller.abort(), currentTimeout);
|
|
551
|
+
|
|
552
|
+
const response = await fetch(apiUrl, {
|
|
553
|
+
method: 'POST',
|
|
554
|
+
body: params,
|
|
555
|
+
headers: new fetch.Headers(headers),
|
|
556
|
+
signal: controller.signal
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
clearTimeout(timeoutId);
|
|
560
|
+
|
|
561
|
+
if (response.ok) {
|
|
562
|
+
const responseBody = await response.json();
|
|
563
|
+
if (responseBody.status === 'ok') {
|
|
564
|
+
this.lastSuccessfulTransfer = new Date();
|
|
565
|
+
this.app.debug('Track successfully sent to API');
|
|
566
|
+
this.app.setPluginStatus(`Started - last Track sent successfully at ${new Date().toISOString()}`);
|
|
567
|
+
if (this.options.keepFiles) {
|
|
568
|
+
const filename = new Date().toJSON().slice(0, 19).replace(/:/g, '') + '-nfl-track.jsonl';
|
|
569
|
+
this.app.debug('moving and keeping track file: ', filename);
|
|
570
|
+
await fs.move(path.join(this.options.trackDir, routeSaveName), path.join(this.options.trackDir, filename));
|
|
571
|
+
} else {
|
|
572
|
+
this.app.debug('Deleting track file');
|
|
573
|
+
await fs.remove(path.join(this.options.trackDir, routeSaveName));
|
|
574
|
+
}
|
|
575
|
+
return; // Erfolg - beende Funktion
|
|
444
576
|
} else {
|
|
445
|
-
this.app.debug('
|
|
446
|
-
|
|
577
|
+
this.app.debug('Could not send track to API, returned response json:', responseBody);
|
|
578
|
+
// Bei API-Fehler nicht erneut versuchen
|
|
579
|
+
this.app.setPluginError(`Failed to send track - API returned error.`);
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
} else {
|
|
583
|
+
this.app.debug('Could not send track to API, returned response code:', response.status, response.statusText);
|
|
584
|
+
// Bei 4xx Fehler nicht erneut versuchen
|
|
585
|
+
if (response.status >= 400 && response.status < 500) {
|
|
586
|
+
this.app.setPluginError(`Failed to send track - HTTP ${response.status}.`);
|
|
587
|
+
return;
|
|
447
588
|
}
|
|
589
|
+
// Bei 5xx Fehler retry
|
|
590
|
+
throw new Error(`HTTP ${response.status}`);
|
|
591
|
+
}
|
|
592
|
+
} catch (err) {
|
|
593
|
+
this.app.debug(`Attempt ${attempt} failed:`, err.message);
|
|
594
|
+
|
|
595
|
+
// Bei letztem Versuch Fehler setzen
|
|
596
|
+
if (attempt === maxRetries) {
|
|
597
|
+
this.app.debug('Could not send track to API after', maxRetries, 'attempts:', err);
|
|
598
|
+
this.app.setPluginError(`Failed to send track after ${maxRetries} attempts - check logs for details.`);
|
|
448
599
|
} else {
|
|
449
|
-
|
|
450
|
-
|
|
600
|
+
// Kurze Pause vor nächstem Versuch
|
|
601
|
+
const waitTime = 2000 * attempt; // 2s, 4s
|
|
602
|
+
this.app.debug(`Waiting ${waitTime}ms before retry...`);
|
|
603
|
+
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
451
604
|
}
|
|
452
|
-
} else {
|
|
453
|
-
this.app.debug('Could not send track to API, returned response code:', response.status, response.statusText);
|
|
454
|
-
this.app.setPluginError(`Failed to send track - check logs for details.`);
|
|
455
605
|
}
|
|
456
|
-
} catch (err) {
|
|
457
|
-
this.app.debug('Could not send track to API due to error:', err);
|
|
458
|
-
this.app.setPluginError(`Failed to send track - check logs for details.`);
|
|
459
606
|
}
|
|
460
607
|
}
|
|
461
|
-
|
|
462
608
|
async createTrack(inputPath) {
|
|
463
609
|
const fileStream = fs.createReadStream(inputPath);
|
|
464
610
|
const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity });
|