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.
Files changed (4) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/README.md +11 -8
  3. package/index.js +289 -143
  4. 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
- An internet connection is required in order to update NFL.
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 = 'SignalK to Noforeland';
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
- getSchema() {
34
- return {
35
- title: this.pluginName,
36
- description: 'Some parameters need for use',
37
- type: 'object',
38
- required: ['apiCron', 'boatApiKey'],
39
- properties: {
40
- trackFrequency: {
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: 'Position tracking frequency in seconds.',
43
- description: 'To keep file sizes small we only log positions once in a while (unless you set this value to 0)',
44
- default: 60
45
- },
46
- minMove: {
47
- type: 'number',
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
- // normalize options
120
- this.options = Object.assign({}, options);
121
- if (!this.options.trackDir) this.options.trackDir = defaultTracksDir;
122
- if (!path.isAbsolute(this.options.trackDir)) {
123
- this.options.trackDir = path.join(__dirname, this.options.trackDir);
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
- if (!this.createDir(this.options.trackDir)) {
127
- this.stop();
128
- return;
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
- this.app.debug('track logger started, now logging to', this.options.trackDir);
132
- this.app.setPluginStatus(`Started`);
133
- this.upSince = new Date().getTime();
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
- // adjust default CRON if unchanged
136
- if (!this.options.apiCron || this.options.apiCron === '*/10 * * * *') {
137
- const startMinute = Math.floor(Math.random() * 10);
138
- const startSecond = Math.floor(Math.random() * 60);
139
- this.options.apiCron = `${startSecond} ${startMinute}/10 * * * *`;
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
- this.app.debug('Setting CRON to ', this.options.apiCron);
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
- // subscribe and logging
145
- this.doLogging();
244
+ // subscribe and logging
245
+ this.doLogging();
146
246
 
147
- // start cron job
148
- this.cron = new CronJob(this.options.apiCron, this.interval.bind(this));
149
- this.cron.start();
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
- // Inform user about last saved point
284
- const lastSaveTime = new Date().toLocaleString();
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
- async testInternet() {
469
+ async testInternet() {
371
470
  const dns = require('dns').promises;
372
471
 
373
472
  this.app.debug('testing internet connection');
374
473
 
375
- try {
376
- // Force IPv4 DNS lookup with timeout
377
- const timeoutMs = this.options.internetTestTimeout || internetTestTimeout;
378
- const addresses = await Promise.race([
379
- dns.resolve4(internetTestAddress),
380
- new Promise((_, reject) =>
381
- setTimeout(() => reject(new Error('DNS timeout')), timeoutMs)
382
- )
383
- ]);
384
-
385
- if (addresses && addresses.length > 0) {
386
- this.app.debug('internet connection = true, resolved IPv4:', addresses[0]);
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
- } else {
389
- this.app.debug('internet connection = false, no IPv4 addresses found');
390
- return false;
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
- try {
433
- const response = await fetch(apiUrl, { method: 'POST', body: params, headers: new fetch.Headers(headers) });
434
- if (response.ok) {
435
- const responseBody = await response.json();
436
- if (responseBody.status === 'ok') {
437
- this.lastSuccessfulTransfer = new Date();
438
- this.app.debug('Track successfully sent to API');
439
- this.app.setPluginStatus(`Started - last Track sent successfully at ${new Date().toLocaleString()}`);
440
- if (this.options.keepFiles) {
441
- const filename = new Date().toJSON().slice(0, 19).replace(/:/g, '') + '-nfl-track.jsonl';
442
- this.app.debug('moving and keeping track file: ', filename);
443
- await fs.move(path.join(this.options.trackDir, routeSaveName), path.join(this.options.trackDir, filename));
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('Deleting track file');
446
- await fs.remove(path.join(this.options.trackDir, routeSaveName));
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
- this.app.debug('Could not send track to API, returned response json:', responseBody);
450
- this.app.setPluginError(`Failed to send track - check logs for details.`);
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 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signalk-to-noforeignland",
3
- "version": "0.1.24",
3
+ "version": "0.1.25",
4
4
  "description": "SignalK track logger to noforeignland.com ",
5
5
  "main": "index.js",
6
6
  "keywords": [