atmosx-nwws-parser 1.0.185 → 1.0.201

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 (78) hide show
  1. package/README.md +3 -86
  2. package/dist/cjs/bootstrap.cjs +1000 -0
  3. package/dist/cjs/database.cjs +1105 -0
  4. package/dist/cjs/dictionaries/awips.cjs +370 -0
  5. package/dist/cjs/dictionaries/events.cjs +139 -0
  6. package/dist/cjs/dictionaries/icao.cjs +265 -0
  7. package/dist/cjs/dictionaries/offshore.cjs +40 -0
  8. package/dist/cjs/dictionaries/signatures.cjs +132 -0
  9. package/dist/cjs/eas.cjs +2851 -0
  10. package/dist/cjs/helper.cjs +3008 -0
  11. package/dist/cjs/parsers/events.cjs +2851 -0
  12. package/dist/cjs/parsers/stanza.cjs +1099 -0
  13. package/dist/cjs/parsers/text.cjs +1133 -0
  14. package/dist/cjs/parsers/types/api.cjs +2851 -0
  15. package/dist/cjs/parsers/types/cap.cjs +2851 -0
  16. package/dist/cjs/parsers/types/text.cjs +2851 -0
  17. package/dist/cjs/parsers/types/ugc.cjs +2851 -0
  18. package/dist/cjs/parsers/types/vtec.cjs +2851 -0
  19. package/dist/cjs/parsers/ugc.cjs +1130 -0
  20. package/dist/cjs/parsers/vtec.cjs +1051 -0
  21. package/dist/cjs/types.cjs +17 -0
  22. package/dist/cjs/utils.cjs +2851 -0
  23. package/dist/cjs/xmpp.cjs +2851 -0
  24. package/dist/esm/bootstrap.mjs +963 -0
  25. package/dist/esm/database.mjs +1070 -0
  26. package/dist/esm/dictionaries/awips.mjs +346 -0
  27. package/dist/esm/dictionaries/events.mjs +111 -0
  28. package/dist/esm/dictionaries/icao.mjs +241 -0
  29. package/dist/esm/dictionaries/offshore.mjs +16 -0
  30. package/dist/esm/dictionaries/signatures.mjs +106 -0
  31. package/dist/esm/eas.mjs +2818 -0
  32. package/dist/esm/helper.mjs +2968 -0
  33. package/dist/esm/parsers/events.mjs +2818 -0
  34. package/dist/esm/parsers/stanza.mjs +1063 -0
  35. package/dist/esm/parsers/text.mjs +1097 -0
  36. package/dist/esm/parsers/types/api.mjs +2818 -0
  37. package/dist/esm/parsers/types/cap.mjs +2818 -0
  38. package/dist/esm/parsers/types/text.mjs +2818 -0
  39. package/dist/esm/parsers/types/ugc.mjs +2818 -0
  40. package/dist/esm/parsers/types/vtec.mjs +2818 -0
  41. package/dist/esm/parsers/ugc.mjs +1095 -0
  42. package/dist/esm/parsers/vtec.mjs +1016 -0
  43. package/dist/esm/types.mjs +0 -0
  44. package/dist/esm/utils.mjs +2818 -0
  45. package/dist/esm/xmpp.mjs +2818 -0
  46. package/package.json +47 -29
  47. package/src/bootstrap.ts +171 -0
  48. package/src/database.ts +99 -0
  49. package/src/dictionaries/awips.ts +342 -0
  50. package/src/dictionaries/events.ts +109 -0
  51. package/src/dictionaries/icao.ts +237 -0
  52. package/src/dictionaries/offshore.ts +12 -0
  53. package/src/dictionaries/signatures.ts +103 -0
  54. package/src/eas.ts +428 -0
  55. package/src/helper.ts +167 -0
  56. package/src/parsers/events.ts +289 -0
  57. package/src/parsers/stanza.ts +103 -0
  58. package/src/parsers/text.ts +167 -0
  59. package/src/parsers/types/api.ts +94 -0
  60. package/src/parsers/types/cap.ts +89 -0
  61. package/src/parsers/types/text.ts +54 -0
  62. package/src/parsers/types/ugc.ts +85 -0
  63. package/src/parsers/types/vtec.ts +60 -0
  64. package/src/parsers/ugc.ts +148 -0
  65. package/src/parsers/vtec.ts +66 -0
  66. package/src/types.ts +187 -0
  67. package/src/utils.ts +217 -0
  68. package/src/xmpp.ts +123 -0
  69. package/test.js +1 -36
  70. package/tsconfig.json +14 -0
  71. package/tsup.config.ts +11 -0
  72. package/bootstrap.js +0 -112
  73. package/index.js +0 -264
  74. package/src/events.js +0 -339
  75. package/src/stanza.js +0 -105
  76. package/src/text.js +0 -108
  77. package/src/ugc.js +0 -115
  78. package/src/vtec.js +0 -89
package/src/types.ts ADDED
@@ -0,0 +1,187 @@
1
+ /*
2
+ _ _ __ __
3
+ /\ | | | | (_) \ \ / /
4
+ / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
+ / /\ \| __| '_ ` _ \ / _ \/ __| '_ \| '_ \ / _ \ '__| |/ __| > <
6
+ / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
+ /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
+ | |
9
+ |_|
10
+
11
+ Written by: k3yomi@GitHub
12
+ */
13
+
14
+ interface GlobalSettings {
15
+ useParentEvents: boolean,
16
+ betterEventParsing: boolean
17
+ easSettings: {
18
+ easAlerts: string[],
19
+ easDirectory: string,
20
+ easIntroWav: string | null
21
+ },
22
+ alertFiltering: {
23
+ filteredEvents: string[],
24
+ filteredICOAs: string[],
25
+ ignoredICOAs: string[],
26
+ ugcFilter: string[],
27
+ stateFilter: string[],
28
+ ignoredEvents: string[],
29
+ checkExpired: boolean
30
+ },
31
+ }
32
+
33
+ interface DefaultParameters {
34
+ wmo: string,
35
+ source: string,
36
+ max_hail_size: string,
37
+ max_wind_gust: string,
38
+ damage_threat: string,
39
+ tornado_detection: string,
40
+ flood_detection: string,
41
+ discussion_tornado_intensity: string,
42
+ discussion_wind_intensity: string,
43
+ discussion_hail_intensity: string
44
+ WMOidentifier?: string[],
45
+ VTEC?: string,
46
+ maxHailSize?: string,
47
+ maxWindGust?: string,
48
+ thunderstormDamageThreat?: string[],
49
+ tornadoDetection?: string[],
50
+ waterspoutDetection?: string[],
51
+ floodDetection?: string[],
52
+ AWIPSidentifier?: string[],
53
+ NWSheadline?: string[],
54
+ }
55
+
56
+ interface DefaultAttributes {
57
+ xmlns?: string,
58
+ id?: string,
59
+ issue?: string,
60
+ ttaaii?: string,
61
+ cccc?: string,
62
+ awipsid?: string,
63
+ getAwip?: Record<string, string>
64
+ }
65
+
66
+ interface ClientReconnectSettings {
67
+ canReconnect: boolean,
68
+ currentInterval: number
69
+ }
70
+
71
+ interface ClientCredentialSettings {
72
+ username: string,
73
+ password: string,
74
+ nickname: string
75
+ }
76
+
77
+ interface ClientCacheSettings {
78
+ read: boolean,
79
+ maxSizeMB: number,
80
+ directory: string,
81
+ maxHistory: number
82
+ }
83
+
84
+ interface ClientAlertSettings {
85
+ isCapOnly: boolean,
86
+ isShapefileUGC: boolean
87
+ }
88
+
89
+ interface NoaaWeatherWireServiceSettings {
90
+ clientReconnections: ClientReconnectSettings,
91
+ clientCredentials: ClientCredentialSettings,
92
+ cache: ClientCacheSettings,
93
+ alertPreferences: ClientAlertSettings
94
+ }
95
+
96
+ interface NationalWeatherServiceSettings {
97
+ checkInterval: number,
98
+ endpoint: string
99
+ }
100
+
101
+ export interface EnhancedEventCondition {
102
+ description?: string;
103
+ condition?: (value: string) => boolean;
104
+ }
105
+
106
+ export interface TypeAttributes {
107
+ awipsType?: Record<string, string>,
108
+ isCap: boolean,
109
+ awipsid?: string,
110
+ raw: boolean,
111
+ issue?: string,
112
+ type?: string,
113
+ prefix?: string,
114
+ getAwip?: { type: string, prefix: string }
115
+ }
116
+
117
+ export interface NationalWeatherServiceResponse {
118
+ error: boolean,
119
+ message?: { features: TypeAlert[] }
120
+ }
121
+
122
+ export interface VTECParsed {
123
+ raw: string,
124
+ tracking: string,
125
+ event: string,
126
+ status: string,
127
+ wmo: string,
128
+ expires: string
129
+ }
130
+ export interface UGCParsed {
131
+ zones: string[],
132
+ locations: string[],
133
+ expiry: Date | null,
134
+ polygon: [number, number][]
135
+ }
136
+
137
+ export interface BaseProperties {
138
+ event?: string,
139
+ locations: string,
140
+ issued: string,
141
+ expires: string,
142
+ geocode: { UGC: string[] },
143
+ description: string,
144
+ sender_name: string,
145
+ sender_icao: string,
146
+ attributes: DefaultAttributes,
147
+ parameters: DefaultParameters,
148
+ geometry: { type?: string, coordinates?: [number, number][] } | null
149
+ messageType?: string,
150
+ sent?: string,
151
+ areaDesc?: string,
152
+ }
153
+
154
+ export interface TypeAlert {
155
+ performance: number,
156
+ tracking: string,
157
+ header: string,
158
+ vtec: string,
159
+ history: { description: string, issued: string, type: string }[],
160
+ properties: BaseProperties
161
+ geometry: { type?: string, coordinates?: [number, number][] } | null
162
+ }
163
+
164
+ export interface ClientSettings {
165
+ database?: string,
166
+ isNWWS: boolean,
167
+ NoaaWeatherWireService: NoaaWeatherWireServiceSettings,
168
+ NationalWeatherService: NationalWeatherServiceSettings,
169
+ global: GlobalSettings,
170
+ }
171
+
172
+ export interface TypeCompiled {
173
+ message: string | null,
174
+ attributes: DefaultAttributes,
175
+ isCap: boolean,
176
+ isApi: boolean,
177
+ isCapDescription: boolean,
178
+ isVtec: boolean,
179
+ isUGC: boolean,
180
+ getAwip?: { type: string, prefix: string },
181
+ awipsType: { type: string, prefix: string } | null,
182
+ awipsPrefix?: string,
183
+ ignore: boolean,
184
+ }
185
+
186
+
187
+ export type HTTPSettings = { timeout?: number, headers?: Record<string, string> }
package/src/utils.ts ADDED
@@ -0,0 +1,217 @@
1
+ /*
2
+ _ _ __ __
3
+ /\ | | | | (_) \ \ / /
4
+ / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
+ / /\ \| __| "_ ` _ \ / _ \/ __| "_ \| "_ \ / _ \ "__| |/ __| > <
6
+ / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
+ /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
+ | |
9
+ |_|
10
+
11
+ Written by: KiyoWx (k3yomi)
12
+ */
13
+
14
+
15
+ import * as loader from './bootstrap';
16
+ import * as types from './types';
17
+ import StanzaParser from './parsers/stanza';
18
+ import EventParser from './parsers/events';
19
+ import Xmpp from './xmpp';
20
+
21
+ export class Utils {
22
+
23
+ /**
24
+ * Zzzzzzz... yeah not much to explain here. Simple sleep function that returns a promise after the specified milliseconds.
25
+ *
26
+ * @public
27
+ * @static
28
+ * @async
29
+ * @param {number} ms
30
+ * @returns {Promise<void>}
31
+ */
32
+ public static async sleep(ms: number): Promise<void> {
33
+ return new Promise(resolve => setTimeout(resolve, ms));
34
+ }
35
+
36
+ /**
37
+ * loadCollectionCache reads cached alert files from the specified cache directory and processes them.
38
+ *
39
+ * @public
40
+ * @static
41
+ * @async
42
+ * @returns {Promise<void>}
43
+ */
44
+ public static async loadCollectionCache(): Promise<void> {
45
+ try {
46
+ const settings = loader.settings as types.ClientSettings;
47
+ if (settings.NoaaWeatherWireService.cache.read && settings.NoaaWeatherWireService.cache.directory) {
48
+ if (!loader.packages.fs.existsSync(settings.NoaaWeatherWireService.cache.directory)) return;
49
+ const cacheDir = settings.NoaaWeatherWireService.cache.directory;
50
+ const getAllFiles = loader.packages.fs.readdirSync(cacheDir).filter((file: string) => file.endsWith('.bin') && file.startsWith('cache-'));
51
+ for (const file of getAllFiles) {
52
+ const filepath = loader.packages.path.join(cacheDir, file);
53
+ const readFile = loader.packages.fs.readFileSync(filepath, { encoding: 'utf-8' });
54
+ const readSize = loader.packages.fs.statSync(filepath).size;
55
+ if (readSize == 0) { continue; }
56
+ const isCap = readFile.includes(`<?xml`);
57
+ if (isCap && !settings.NoaaWeatherWireService.alertPreferences.isCapOnly) continue;
58
+ if (!isCap && settings.NoaaWeatherWireService.alertPreferences.isCapOnly) continue;
59
+ const validate = StanzaParser.validate(readFile, { awipsid: file, isCap: isCap, raw: true, issue: undefined });
60
+ await EventParser.eventHandler(validate);
61
+ }
62
+ }
63
+ } catch (error: any) {
64
+ loader.cache.events.emit('onError', { code: 'error-load-cache', message: `Failed to load cache: ${error.message}`});
65
+ }
66
+ }
67
+
68
+ /**
69
+ * loadGeoJsonData fetches GeoJSON data from the National Weather Service endpoint and processes each alert.
70
+ *
71
+ * @public
72
+ * @static
73
+ * @async
74
+ * @returns {Promise<void>}
75
+ */
76
+ public static async loadGeoJsonData(): Promise<void> {
77
+ try {
78
+ const settings = loader.settings as types.ClientSettings;
79
+ const response = await this.createHttpRequest(settings.NationalWeatherService.endpoint) as types.NationalWeatherServiceResponse
80
+ if (!response.error) {
81
+ EventParser.eventHandler({message: JSON.stringify(response.message), attributes: {}, isCap: true, isApi: true, isVtec: false, isUGC: false, isCapDescription: false, awipsType: { type: 'api', prefix: 'AP' }, ignore: false});
82
+ }
83
+ } catch (error: any) {
84
+ loader.cache.events.emit('onError', { code: 'error-fetching-nws-data', message: `Failed to fetch NWS data: ${error.message}`});
85
+ }
86
+ }
87
+
88
+ /**
89
+ * detectUncaughtExceptions sets up a global handler for uncaught exceptions in the Node.js process,
90
+ *
91
+ * @public
92
+ * @static
93
+ */
94
+ public static detectUncaughtExceptions(): void {
95
+ if (process.listeners('uncaughtException').some(l => l.name === 'uncaughtExceptionHandler')) return;
96
+ process.on(`uncaughtException`, (error: Error) => {
97
+ loader.cache.events.emit(`onError`, {message: `Uncaught Exception: ${error.message}`, code: `error-uncaught-exception`, stack: error.stack});
98
+ })
99
+ }
100
+
101
+ /**
102
+ * createHttpRequest performs an HTTP GET request to the specified URL with optional settings.
103
+ *
104
+ * @public
105
+ * @static
106
+ * @async
107
+ * @param {string} url
108
+ * @param {?types.HTTPSettings} [options]
109
+ * @returns {unknown}
110
+ */
111
+ public static async createHttpRequest(url: string, options?: types.HTTPSettings) {
112
+ const defaultOptions = {
113
+ timeout: 10000,
114
+ headers: {
115
+ "User-Agent": "AtmosphericX",
116
+ "Accept": "application/geo+json, text/plain, */*; q=0.9",
117
+ "Accept-Language": "en-US,en;q=0.9"
118
+ }
119
+ };
120
+ const requestOptions = {
121
+ ...defaultOptions,
122
+ ...options,
123
+ headers: { ...defaultOptions.headers, ...(options?.headers ?? {}) }
124
+ };
125
+ try {
126
+ const resp = await loader.packages.axios.get(url, {
127
+ headers: requestOptions.headers,
128
+ timeout: requestOptions.timeout,
129
+ maxRedirects: 0,
130
+ validateStatus: (status) => status === 200 || status === 500
131
+ });
132
+ return { error: false, message: resp.data };
133
+ } catch (err: any) {
134
+ return { error: true, message: err?.message ?? String(err) };
135
+ }
136
+ }
137
+
138
+ /**
139
+ * garbageCollectionCache removes files from the cache directory that exceed the specified maximum file size in megabytes.
140
+ *
141
+ * @public
142
+ * @static
143
+ * @param {number} maxFileMegabytes
144
+ */
145
+ public static garbageCollectionCache(maxFileMegabytes: number): void {
146
+ try {
147
+ const settings = loader.settings as types.ClientSettings;
148
+ if (!settings.NoaaWeatherWireService.cache.directory) return;
149
+ if (!loader.packages.fs.existsSync(settings.NoaaWeatherWireService.cache.directory)) return;
150
+ const maxBytes = maxFileMegabytes * 1024 * 1024;
151
+ const cacheDirectory = settings.NoaaWeatherWireService.cache.directory;
152
+ const stackFiles: string[] = [cacheDirectory], files: {
153
+ file: string,
154
+ size: number,
155
+ }[] = [];
156
+ while (stackFiles.length) {
157
+ const currentDirectory = stackFiles.pop();
158
+ loader.packages.fs.readdirSync(currentDirectory).forEach((file: string) => {
159
+ const fullPath = loader.packages.path.join(currentDirectory, file);
160
+ const stat = loader.packages.fs.statSync(fullPath)
161
+ if (stat.isDirectory()) stackFiles.push(fullPath) ;
162
+ else files.push({ file: fullPath, size: stat.size });
163
+ })
164
+ }
165
+ if (!files.length) return;
166
+ files.forEach(f => {
167
+ if (f.size > maxBytes) loader.packages.fs.unlinkSync(f.file);
168
+ })
169
+ } catch (error: any) {
170
+ loader.cache.events.emit('onError', { code: 'error-garbage-collection', message: `Failed to perform garbage collection: ${error.message}`});
171
+ }
172
+ }
173
+
174
+ /**
175
+ * handleCronJob performs periodic tasks based on whether the client is connected to NWWS or fetching data from NWS.
176
+ *
177
+ * @public
178
+ * @static
179
+ * @param {boolean} isNwws
180
+ */
181
+ public static handleCronJob(isNwws: boolean): void {
182
+ try {
183
+ const settings = loader.settings as types.ClientSettings;
184
+ if (isNwws) {
185
+ if (settings.NoaaWeatherWireService.cache.read ) void this.garbageCollectionCache(settings.NoaaWeatherWireService.cache.maxSizeMB);
186
+ if (settings.NoaaWeatherWireService.clientReconnections.canReconnect ) void Xmpp.isSessionReconnectionEligible(settings.NoaaWeatherWireService.clientReconnections.currentInterval);
187
+ } else {
188
+ void this.loadGeoJsonData();
189
+ }
190
+ } catch (error: any) {
191
+ loader.cache.events.emit('onError', { code: 'error-cron-job', message: `Failed to perform scheduled tasks: ${error.message}`});
192
+ }
193
+ }
194
+
195
+ /**
196
+ * mergeClientSettings merges user-provided settings into the existing client settings, allowing for nested objects to be merged correctly.
197
+ *
198
+ * @public
199
+ * @static
200
+ * @param {Record<string, any>} target
201
+ * @param {Record<string, any>} settings
202
+ */
203
+ public static mergeClientSettings(target: Record<string, any>, settings: Record<string, any>): void {
204
+ for (const key in settings) {
205
+ if (settings.hasOwnProperty(key)) {
206
+ if (typeof settings[key] === 'object' && settings[key] !== null && !Array.isArray(settings[key])) {
207
+ if (!target[key] || typeof target[key] !== 'object') { target[key] = {}; }
208
+ this.mergeClientSettings(target[key], settings[key]);
209
+ } else {
210
+ target[key] = settings[key];
211
+ }
212
+ }
213
+ }
214
+ }
215
+ }
216
+
217
+ export default Utils;
package/src/xmpp.ts ADDED
@@ -0,0 +1,123 @@
1
+ /*
2
+ _ _ __ __
3
+ /\ | | | | (_) \ \ / /
4
+ / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
+ / /\ \| __| "_ ` _ \ / _ \/ __| "_ \| "_ \ / _ \ "__| |/ __| > <
6
+ / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
+ /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
+ | |
9
+ |_|
10
+
11
+ Written by: KiyoWx (k3yomi)
12
+ */
13
+
14
+
15
+ import * as loader from './bootstrap';
16
+ import * as types from './types';
17
+ import Utils from './utils';
18
+ import StanzaParser from './parsers/stanza';
19
+ import Database from './database';
20
+ import EventParser from './parsers/events';
21
+
22
+
23
+ export class Xmpp {
24
+
25
+ /**
26
+ * isSessionReconnectionEligible checks if the XMPP session is eligible for reconnection based on the last
27
+ * received stanza time and current interval.
28
+ *
29
+ * @public
30
+ * @static
31
+ * @async
32
+ * @param {number} currentInterval
33
+ * @returns {Promise<void>}
34
+ */
35
+ public static async isSessionReconnectionEligible(currentInterval: number): Promise<void> {
36
+ const settings = loader.settings as types.ClientSettings;
37
+ if ((loader.cache.isConnected || loader.cache.sigHalt ) && loader.cache.session) {
38
+ const lastStanza = Date.now() - loader.cache.lastStanza;
39
+ if (lastStanza >= (currentInterval * 1000)) {
40
+ if (!loader.cache.attemptingReconnect) {
41
+ loader.cache.attemptingReconnect = true;
42
+ loader.cache.isConnected = false;
43
+ loader.cache.totalReconnects += 1;
44
+ loader.cache.events.emit(`onReconnect`, { reconnects: loader.cache.totalReconnects, lastStanza: lastStanza, lastName: settings.NoaaWeatherWireService.clientCredentials.nickname})
45
+ await loader.cache.session.stop().catch(() => {});
46
+ await loader.cache.session.start().catch(() => {});
47
+ }
48
+ }
49
+ }
50
+ }
51
+
52
+ /**
53
+ * deploySession initializes and starts the XMPP client session, setting up event listeners for
54
+ * connection management and message handling. This function is specifically tailored for
55
+ * NoaaWeatherWireService and connects to their XMPP server.
56
+ *
57
+ * @public
58
+ * @static
59
+ * @async
60
+ * @returns {Promise<void>}
61
+ */
62
+ public static async deploySession(): Promise<void> {
63
+ const settings = loader.settings as types.ClientSettings
64
+ loader.cache.session = loader.packages.xmpp.client({
65
+ service: `xmpp://nwws-oi.weather.gov`,
66
+ domain: `nwws-oi.weather.gov`,
67
+ username: settings.NoaaWeatherWireService.clientCredentials.username,
68
+ password: settings.NoaaWeatherWireService.clientCredentials.password,
69
+ });
70
+ settings.NoaaWeatherWireService.clientCredentials.nickname ??= settings.NoaaWeatherWireService.clientCredentials.username;
71
+ loader.cache.session.on(`online`, async (address: string) => {
72
+ if (loader.cache.lastConnect && Date.now() - loader.cache.lastConnect < 10 * 1000) {
73
+ loader.cache.sigHalt = true;
74
+ Utils.sleep(2 * 1000).then(async () => {
75
+ await loader.cache.session.stop()
76
+ });
77
+ loader.cache.events.emit(`onError`, { code: `error-reconnecting-too-fast`, message: `The client is attempting to reconnect too fast. Please wait a few seconds before trying again.` });
78
+ return;
79
+ }
80
+ loader.cache.isConnected = true;
81
+ loader.cache.sigHalt = false;
82
+ loader.cache.lastConnect = Date.now();
83
+ loader.cache.session.send(loader.packages.xmpp.xml('presence', { to: `nwws@conference.nwws-oi.weather.gov/${settings.NoaaWeatherWireService.clientCredentials.nickname}`, xmlns: 'http://jabber.org/protocol/muc' }))
84
+ loader.cache.session.send(loader.packages.xmpp.xml('presence', { to: `nwws@conference.nwws-oi.weather.gov`, type: 'available' }))
85
+ loader.cache.events.emit(`onConnection`, settings.NoaaWeatherWireService.clientCredentials.nickname)
86
+ if (loader.cache.attemptingReconnect) {
87
+ Utils.sleep(15 * 1000).then(() => { loader.cache.attemptingReconnect = false; })
88
+ }
89
+ });
90
+ loader.cache.session.on(`offline`, async () => {
91
+ loader.cache.isConnected = false;
92
+ loader.cache.sigHalt = true;
93
+ loader.cache.events.emit(`onError`, { code: `connection-lost`, message: `XMPP connection went offline` });
94
+ });
95
+ loader.cache.session.on(`error`, async (error: Error) => {
96
+ loader.cache.isConnected = false;
97
+ loader.cache.sigHalt = true;
98
+ loader.cache.events.emit(`onError`, { code: `connection-error`, message: error.message });
99
+ });
100
+ loader.cache.session.on(`stanza`, async (stanza: any) => {
101
+ try {
102
+ loader.cache.lastStanza = Date.now()
103
+ if (stanza.is(`message`)) {
104
+ const validate = StanzaParser.validate(stanza);
105
+ if ( validate.ignore || (validate.isCap && !settings.NoaaWeatherWireService.alertPreferences.isCapOnly) || (!validate.isCap && settings.NoaaWeatherWireService.alertPreferences.isCapOnly) || (validate.isCap && !validate.isCapDescription) ) return;
106
+ EventParser.eventHandler(validate);
107
+ loader.cache.events.emit(`onMessage`, validate)
108
+ Database.stanzaCacheImport(JSON.stringify(validate))
109
+ }
110
+ if (stanza.is(`presence`) && stanza.attrs.from && stanza.attrs.from.startsWith('nwws@conference.nwws-oi.weather.gov/')) {
111
+ const occupant = stanza.attrs.from.split('/').slice(1).join('/');
112
+ loader.cache.events.emit('onOccupant', { occupant, type: stanza.attrs.type === 'unavailable' ? 'unavailable' : 'available' });
113
+ }
114
+ } catch (e) {
115
+ loader.cache.events.emit(`onError`, {code: `error-processing-stanza`, message: (e as Error).message})
116
+ }
117
+ });
118
+ await loader.cache.session.start()
119
+ }
120
+
121
+ }
122
+
123
+ export default Xmpp;
package/test.js CHANGED
@@ -1,36 +1 @@
1
- AtmosXWireParser = require(`./index.js`);
2
-
3
- let Client = new AtmosXWireParser({
4
- alertSettings: {
5
- onlyCap: false, // Set to true to only receive CAP messages only
6
- betterEvents: true, // Set to true to receive better event handling
7
- ugcPolygons: false, // Set to true to receive UGC Polygons instead of reading from raw products.
8
- expiryCheck: true, // Set to true to filter out expired alerts
9
- filteredAlerts: [] // Alerts you want to only log, leave empty to receive all alerts (Ex. ["Tornado Warning", "Radar Indicated Tornado Warning"])
10
- },
11
- xmpp: {
12
- reconnect: true, // Set to true to enable automatic reconnection if you lose connection
13
- reconnectInterval: 60, // Interval in seconds to attempt reconnection
14
- },
15
- cacheSettings: {
16
- maxMegabytes: 2, // Maximum cache size in megabytes
17
- cacheDir: `./cache`, // Directory for cache files
18
- readCache: false, // Set to true if you wish to reupload the cache from earlier
19
- },
20
- authentication: {
21
- username: `USERNAME_HERE`, // Your XMPP username
22
- password: `PASSWORD_HERE`, // Your XMPP password
23
- display: `DISPLAY_NAME` // Display name for your XMPP client
24
- },
25
- database: `./database.db`, // Path to the SQLite database file (It will be created if it doesn't exist and will be used to store UGC counties and zones.)
26
- });
27
-
28
- Client.onEvent(`onAlert`, (alert) => {console.log(alert)});
29
- Client.onEvent(`onStormReport`, (report) => {});
30
- Client.onEvent(`onMesoscaleDiscussion`, (discussions) => {});
31
- Client.onEvent(`onMessage`, (message) => {});
32
- Client.onEvent(`onOccupant`, (occupant) => {});
33
- Client.onEvent(`onError`, (error) => {console.log(error)});
34
- Client.onEvent(`onReconnect`, (service) => {
35
- Client.setDisplayName(`${username} (x${service.reconnects})`)
36
- })
1
+ // TODO:
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES6",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Node",
6
+ "outDir": "dist",
7
+ "declaration": true,
8
+ "declarationDir": "dist/types",
9
+ "esModuleInterop": true,
10
+ "allowSyntheticDefaultImports": true,
11
+ "skipLibCheck": true
12
+ },
13
+ "include": ["src"]
14
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/**/*.ts'],
5
+ format: ['esm', 'cjs'],
6
+ outDir: 'dist',
7
+ splitting: false,
8
+ clean: true,
9
+ outExtension({ format }) {return {js: format === 'esm' ? '.mjs' : '.cjs',};},
10
+ esbuildOptions(options, context) { options.outdir = `dist/${context.format}`; },
11
+ });