@quotemedia.com/streamer 2.64.0 → 2.66.0

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.
@@ -1,7 +1,7 @@
1
1
  <html>
2
2
 
3
3
  <head>
4
- <script src="qmci-streamer-2.64.0.min.js"></script>
4
+ <script src="qmci-streamer-2.66.0.min.js"></script>
5
5
  </head>
6
6
 
7
7
  <body>
@@ -11,9 +11,17 @@
11
11
  * - Configure and creating stream
12
12
  * - Configure and opening connection
13
13
  * - Set up callback to handle messages
14
- * - Subscribe for News Filters
15
- * - Update for News Filters
16
- * - Unsubscribe for the News Filters
14
+ * - Open a News connection (obtain newsClientId)
15
+ * - Subscribe for News Filters with newsClientId
16
+ * - Handle incoming News messages with rich field display
17
+ * - Differentiate News message types (data, filter, error)
18
+ * - Subscribe multiple concurrent filters with different filterIds
19
+ * - Compare skipHeavyInitialLoad true vs false behavior
20
+ * - Query current News Filter status
21
+ * - Update existing News Filters
22
+ * - Unsubscribe from News Filters
23
+ * - Reconnect News with existing newsClientId and verify filter persistence
24
+ * - Subscribe new filter after reconnect and query status
17
25
  * - Close the connection and stream
18
26
  */
19
27
  window.onload = function() {
@@ -23,10 +31,15 @@
23
31
  * Step 1: Configure your login credentials inside the login method to get an SID
24
32
  * Step 2: Open the streaming connection
25
33
  * Step 3: Add the event listeners and the handlers for the messages
26
- * Step 4: Make News Filters subscription
27
- * Step 4: Update News Filters subscription
28
- * Step 5: Make News Filters unsubscribe
29
- * Step 8: Close stream
34
+ * Step 4: Open News connection via openNews() to obtain a newsClientId
35
+ * Step 5: Subscribe to News Filters with the newsClientId (skipHeavyInitialLoad: false)
36
+ * Step 6: Subscribe a second filter concurrently (skipHeavyInitialLoad: true)
37
+ * Step 7: Query current News Filter status
38
+ * Step 8: Update News Filters
39
+ * Step 9: Unsubscribe from News Filter 1 (keep filter 2 active)
40
+ * Step 10: Reconnect News with existing newsClientId, query filter status
41
+ * Step 11: Subscribe new filter after reconnect, query filter status again
42
+ * Step 12: Close stream
30
43
  */
31
44
 
32
45
  // Log in to get an SID.
@@ -53,7 +66,41 @@
53
66
  // The stream will asynchronously callback with
54
67
  // incoming market data messages.
55
68
  .on("message", function(message) {
56
- print(msgFmt.fmt(message), "dodgerblue")
69
+ /**
70
+ * # News Data Message Fields (type "D18").
71
+ *
72
+ * Available fields on the message object:
73
+ * headline, summary, source, sourceId, symbol,
74
+ * storyId, storyUrl, newsUrl, timestamp, epochtime,
75
+ * lang, excode, exgroup, topic, sector,
76
+ * thumbnailUrl, videoUrl, videoImageUrl,
77
+ * vendorDateId, filterId
78
+ */
79
+ if (message["@T"] === "D18") {
80
+ // Display rich News data fields
81
+ print("--- News Article Received ---", "dodgerblue");
82
+ print(" Headline: " + message.headline, "dodgerblue");
83
+ print(" Source: " + message.source + " (" + message.sourceId + ")", "dodgerblue");
84
+ print(" Symbol: " + message.symbol, "dodgerblue");
85
+ print(" Topic: " + message.topic, "dodgerblue");
86
+ print(" Sector: " + message.sector, "dodgerblue");
87
+ print(" Time: " + new Date(message.timestamp).toLocaleString(), "dodgerblue");
88
+ print(" FilterId: " + message.filterId, "dodgerblue");
89
+ if (message.summary) {
90
+ print(" Summary: " + message.summary.substring(0, 200) + "...", "dodgerblue");
91
+ }
92
+ if (message.storyUrl) {
93
+ print(" Story URL: " + message.storyUrl, "dodgerblue");
94
+ }
95
+ if (message.thumbnailUrl) {
96
+ print(" Thumbnail: " + message.thumbnailUrl, "dodgerblue");
97
+ }
98
+ if (message.videoUrl) {
99
+ print(" Video: " + message.videoUrl, "dodgerblue");
100
+ }
101
+ } else {
102
+ print(msgFmt.fmt(message), "dodgerblue");
103
+ }
57
104
  })
58
105
  // It's recommended to attach an error handler
59
106
  // to help diagnose unexpected errors.
@@ -61,135 +108,275 @@
61
108
  print(err, "red");
62
109
  }).on("close", function(msg) {
63
110
  print("Closed: " + msg);
64
- // To catch and handling the News messages
65
- }).on("newsRemoteMessage", function(msg) {
66
- print("newsRemoteMessage: " + msgFmt.fmt(msg), "green");
111
+ })
112
+ /**
113
+ * # Differentiate News Message Types.
114
+ *
115
+ * The "newsRemoteMessage" event can carry different message types:
116
+ * - "C37" (NEWS_FILTER_MESSAGE): Normal news filter notification
117
+ * - "C38" (NEWS_ERROR_MESSAGE): Error notification with event, code, message fields
118
+ * - "C39" (NEWS_SUCCESS_MESSAGE): Success notification with code, message fields
119
+ *
120
+ * Use the "@T" field to differentiate and handle each type accordingly.
121
+ */
122
+ .on("newsRemoteMessage", function(msg) {
123
+ switch (msg["@T"]) {
124
+ case "C37": // NEWS_FILTER_MESSAGE
125
+ print("[NEWS FILTER] " + msg.message, "green");
126
+ break;
127
+ case "C38": // NEWS_ERROR_MESSAGE
128
+ print("[NEWS ERROR] Event: " + msg.event + ", Code: " + msg.code + ", Message: " + msg.message, "red");
129
+ break;
130
+ case "C39": // NEWS_SUCCESS_MESSAGE
131
+ print("[NEWS SUCCESS] Code: " + msg.code + ", Message: " + msg.message, "limegreen");
132
+ break;
133
+ default:
134
+ print("newsRemoteMessage: " + msgFmt.fmt(msg), "green");
135
+ break;
136
+ }
67
137
  });
68
138
 
69
139
  /**
70
- * Supported filter parameters for Streaming News (as of now):
140
+ * Supported filter parameters for Streaming News:
71
141
  *
72
- * @param src Array of news source identifiers (e.g., "djns", "bwi").
73
- * @param topic Array of topic identifiers.
74
- * @param symbol Array of stock symbols.
75
- * @param excode Array of exchange codes.
76
- * @param exgroup Array of exchange group identifiers.
142
+ * Array filters (use association: "OR" or "AND"):
143
+ * @param src Array of news source identifiers (e.g., "djns", "bwi", "mtn").
144
+ * @param topic Array of topic names (e.g., "Market and Economy").
145
+ * @param sector Array of sector codes (e.g., "101", "103").
146
+ * @param symbol Array of stock symbols (e.g., "AAPL", "GOOG", "^DJT").
147
+ * @param excode Array of exchange codes (e.g., "TSX", "NYE").
148
+ * @param exgroup Array of exchange group identifiers (e.g., "NSD", "DOW").
149
+ * @param keyword Array of keyword strings to match in news content (e.g., "analyst", "sell", "exchange offer").
77
150
  *
78
- * Additional parameters may be supported in future releases.
151
+ * Boolean/value parameters (use association: null):
152
+ * @param summary Boolean. If true, include article summary in the response.
153
+ * @param summlen Number. Maximum length of the summary text (e.g., 100).
154
+ * @param constituent Boolean. If true, include constituent-related news.
155
+ * @param searchByExchange Boolean. If true, search news by exchange.
79
156
  */
80
157
 
81
158
  /**
82
159
  * The Below filter will return the News from
83
- * "source" djns OR bwi, and contains
84
- * "topic" computer OR Games and Multimedia, and contains
85
- * "symbol" GOOG AND AAPL, and contains
86
- * "excode" OTO AND NYE, and contains
87
- * "exgroup" DOW AND NSD.
160
+ * "source" bwi OR djns, and contains
161
+ * "topic" Market and Economy, and contains
162
+ * "sector" 101 OR 103, and contains
163
+ * "symbol" AAPL OR GOOG OR ^DJT, and contains
164
+ * "excode" TSX, and contains
165
+ * "exgroup" NSD, and contains
166
+ * "keyword" analyst OR sell OR exchange offer.
167
+ * Additionally, includes summary (max 100 chars),
168
+ * constituent news, and searches by exchange.
88
169
  */
89
170
  const newsFilterExampleJson1 = [
90
- { name: "src", value: ["djns", "bwi"], association: "OR" },
91
- { name: "topic", value: ["Computer", "Games and Multimedia"], association: "OR" },
92
- { name: "symbol", value: ["GOOG", "AAPL"], association: "AND" },
93
- { name: "excode", value: ["OTO", "NYE"], association: "AND" },
94
- { name: "exgroup", value: ["DOW", "NSD"], association: "AND" }
171
+ { name: "src", value: ["bwi", "djns"], association: "OR" },
172
+ { name: "topic", value: ["Market and Economy"], association: "OR" },
173
+ { name: "sector", value: ["101", "103"], association: "OR" },
174
+ { name: "symbol", value: ["AAPL", "GOOG", "^DJT"], association: "OR" },
175
+ { name: "excode", value: ["TSX"], association: "OR" },
176
+ { name: "exgroup", value: ["NSD"], association: "OR" },
177
+ { name: "keyword", value: ["analyst", "sell", "exchange offer"], association: "OR" },
178
+ { name: "summary", value: true, association: null },
179
+ { name: "summlen", value: 100, association: null },
180
+ { name: "constituent", value: true, association: null },
181
+ { name: "searchByExchange", value: true, association: null }
95
182
  ];
96
183
 
97
184
  /**
98
185
  * The Below filter will return the News from
99
186
  * "source" djns OR mtn OR bwi, and contains
100
- * "topic" computer OR Games and Multimedia OR Entertainment, and contains
187
+ * "topic" Market and Economy OR Entertainment, and contains
188
+ * "sector" 101, and contains
101
189
  * "symbol" GOOG OR AAPL OR COST, and contains
102
- * "excode" OTO OR NYE, and contains
103
- * "exgroup" DOW OR NSD.
190
+ * "excode" TSX OR NYE, and contains
191
+ * "exgroup" DOW OR NSD, and contains
192
+ * "keyword" earnings OR merger.
193
+ * Additionally, includes summary (max 200 chars).
104
194
  */
105
195
  const newsFilterExampleJson2 = [
106
196
  { name: "src", value: ["djns", "mtn", "bwi"], association: "OR" },
107
- { name: "topic", value: ["Computer", "Games and Multimedia", "Entertainment"], association: "OR" },
197
+ { name: "topic", value: ["Market and Economy", "Entertainment"], association: "OR" },
198
+ { name: "sector", value: ["101"], association: "OR" },
108
199
  { name: "symbol", value: ["GOOG", "AAPL", "COST"], association: "OR" },
109
- { name: "excode", value: ["OTO", "NYE"], association: "OR" },
110
- { name: "exgroup", value: ["DOW", "NSD"], association: "OR" }
200
+ { name: "excode", value: ["TSX", "NYE"], association: "OR" },
201
+ { name: "exgroup", value: ["DOW", "NSD"], association: "OR" },
202
+ { name: "keyword", value: ["earnings", "merger"], association: "OR" },
203
+ { name: "summary", value: true, association: null },
204
+ { name: "summlen", value: 200, association: null }
205
+ ];
206
+
207
+ /**
208
+ * A minimal filter targeting only source and symbols,
209
+ * used to demonstrate multiple concurrent filter subscriptions.
210
+ * Not all parameters are required — you can use as few as needed.
211
+ */
212
+ const newsFilterExampleJson3 = [
213
+ { name: "src", value: ["djns"], association: "OR" },
214
+ { name: "symbol", value: ["MSFT", "AMZN", "TSLA"], association: "OR" }
111
215
  ];
112
216
 
113
- const filterId = crypto.randomUUID();
114
- print("News FilterId: " + filterId, "green");
217
+ const filterId1 = crypto.randomUUID();
218
+ const filterId2 = crypto.randomUUID();
219
+ print("News FilterId 1: " + filterId1, "green");
220
+ print("News FilterId 2: " + filterId2, "green");
115
221
 
116
222
  /**
117
- * # Subscribe to News Filters.
223
+ * # Step 4: Open a News connection.
118
224
  *
119
- * - News Filters: Should be provided as a JSON object.
120
- * - Filter ID: A UUID that uniquely identifies each News filter.
121
- * - skipHeavyInitialLoad: Optional. Indicates whether to skip initial heavy-load messages. Defaults to false.
225
+ * openNews() establishes a News connection and returns a newsClientId.
226
+ * - newsClientId: Can be null for a new connection, or pass an existing
227
+ * newsClientId to reconnect to a previous News session.
228
+ * - The returned newsClientId should be saved for potential reconnection.
122
229
  */
123
- stream.subscribeNews(newsFilterExampleJson1, filterId, { skipHeavyInitialLoad: false }, (err, result) => {
230
+ stream.openNews(null, (err, openResult) => {
124
231
  if (err) {
125
- print("Failed to subscribe News filter", "red")
126
- } else {
127
- print("News Connection opened")
128
- print("SubscribeResponse: " + JSON.stringify(result), "green");
232
+ print("Failed to open News connection: " + err, "red");
233
+ return;
234
+ }
235
+
236
+ const newsClientId = openResult.newsClientId;
237
+ print("News connection opened, newsClientId: " + newsClientId, "green");
238
+
239
+ /**
240
+ * # Step 5: Subscribe to the first News Filter.
241
+ *
242
+ * skipHeavyInitialLoad: false
243
+ * - The server will send all buffered/initial news data upon subscription.
244
+ * - This is useful when the client needs the full initial dataset.
245
+ */
246
+ stream.subscribeNews(newsFilterExampleJson1, filterId1, false, newsClientId, (err, result) => {
247
+ if (err) {
248
+ print("Failed to subscribe News filter 1: " + err, "red");
249
+ return;
250
+ }
251
+
252
+ print("News filter 1 subscribed (skipHeavyInitialLoad: false)");
253
+ print("SubscribeResponse 1: " + JSON.stringify(result), "green");
254
+ });
255
+
256
+ /**
257
+ * # Step 6: Subscribe a second filter concurrently with a different filterId.
258
+ *
259
+ * skipHeavyInitialLoad: true
260
+ * - The server will skip sending buffered/initial news data.
261
+ * - Only new incoming news after subscription will be delivered.
262
+ * - This reduces initial bandwidth and is recommended when historical data is not needed.
263
+ *
264
+ * Multiple filters can be active simultaneously, each identified by a unique filterId.
265
+ * Each filter operates independently and can be updated or removed separately.
266
+ */
267
+ stream.subscribeNews(newsFilterExampleJson3, filterId2, true, newsClientId, (err, result) => {
268
+ if (err) {
269
+ print("Failed to subscribe News filter 2: " + err, "red");
270
+ return;
271
+ }
272
+
273
+ print("News filter 2 subscribed (skipHeavyInitialLoad: true)");
274
+ print("SubscribeResponse 2: " + JSON.stringify(result), "green");
275
+ });
276
+
277
+ // Step 7: Query current News Filter status (should show both filters)
278
+ setTimeout(() => {
279
+ print("Get Current Subscription Filter Status");
280
+ stream.fltGetNews((err) => {
281
+ if (err) {
282
+ print("err: " + err, "red");
283
+ }
284
+ });
285
+ }, 3000);
286
+
287
+ // Step 8: Update filter 1 with new criteria
288
+ setTimeout(() => {
289
+ print("Update News Filter 1");
290
+ stream.fltUpdateNews(newsFilterExampleJson2, filterId1, (err, result) => {
291
+ if (err) {
292
+ print("Failed to update News filter: " + err, "red");
293
+ } else {
294
+ print("News filter 1 updated");
295
+ print("Filter Update Response: " + JSON.stringify(result), "green");
296
+ }
297
+ });
298
+ }, 5000);
299
+
300
+ // Query filter status after update
301
+ setTimeout(() => {
302
+ print("Get Current Subscription Filter Status");
303
+ stream.fltGetNews((err) => {
304
+ if (err) {
305
+ print("err: " + err, "red");
306
+ }
307
+ });
308
+ }, 7000);
309
+
310
+ // Step 9: Unsubscribe from both filters
311
+ setTimeout(() => {
312
+ print("Unsubscribe News Filter 1");
313
+ stream.unsubscribeNews(filterId1, (err, result) => {
314
+ if (err) {
315
+ print("Failed to unsubscribe News filter 1: " + err, "red");
316
+ } else {
317
+ print("News filter 1 unsubscribed");
318
+ print("UnsubscribeResponse: " + JSON.stringify(result), "green");
319
+ }
320
+ });
321
+ }, 9000);
322
+
323
+ // Query filter status after unsubscribe (filter 2 should still be active)
324
+ setTimeout(() => {
325
+ print("Get Current Subscription Filter Status");
326
+ stream.fltGetNews((err) => {
327
+ if (err) {
328
+ print("err: " + err, "red");
329
+ }
330
+ });
331
+ }, 11000);
129
332
 
130
- setTimeout(() => {
131
- print("Get Current Subscription Filter Status")
333
+ // Step 10: Demonstrate News reconnection with existing newsClientId
334
+ setTimeout(() => {
335
+ print("--- Reconnecting News with existing newsClientId ---", "orange");
336
+ stream.openNews(newsClientId, (err, reconnectResult) => {
337
+ if (err) {
338
+ print("Failed to reconnect News: " + err, "red");
339
+ return;
340
+ }
341
+ print("News reconnected, newsClientId: " + reconnectResult.newsClientId, "green");
342
+
343
+ // Query filter status after reconnect (filter 2 should still be active)
344
+ print("Get Current Subscription Filter Status");
132
345
  stream.fltGetNews((err) => {
133
346
  if (err) {
134
- print("err: " + err, "red")
135
- }
136
- })
137
- }, 2000);
138
-
139
- setTimeout(() => {
140
- print("Update News Filter")
141
- /**
142
- * # Update to News Filters.
143
- *
144
- * - News Filters: Should be provided as a JSON object.
145
- * - Filter ID: A UUID that uniquely identifies each News filter.
146
- * - skipHeavyInitialLoad: Optional. Indicates whether to skip initial heavy-load messages. Defaults to false.
147
- */
148
- stream.fltUpdateNews(newsFilterExampleJson2, filterId, (err, result) => {
149
- if (err) {
150
- print("Failed to update News filter" + err, "red")
151
- } else {
152
- print("News filter updated")
153
- print("Filter Update Response: " + JSON.stringify(result), "green");
347
+ print("err: " + err, "red");
154
348
  }
155
349
  });
156
- }, 4000)
157
350
 
158
- setTimeout(() => {
159
- print("Get Current Subscription Filter Status")
160
- stream.fltGetNews((err) => {
161
- if (err) {
162
- print("err: " + err, "red")
163
- }
164
- })
165
- }, 6000);
351
+ // Subscribe a new filter after reconnect
352
+ const reconnectFilterId = crypto.randomUUID();
353
+ print("Reconnect FilterId: " + reconnectFilterId, "green");
166
354
 
167
- setTimeout(() => {
168
- print("Unsubscribe News Filter")
169
- stream.fltDeleteNews(filterId, (err) => {
355
+ stream.subscribeNews(newsFilterExampleJson1, reconnectFilterId, true, reconnectResult.newsClientId, (err, result) => {
170
356
  if (err) {
171
- print("err: " + err, "red")
357
+ print("Failed to subscribe after reconnect: " + err, "red");
358
+ return;
172
359
  }
173
- })
174
- }, 8000);
360
+ print("News filter subscribed after reconnect");
361
+ print("ReconnectSubscribeResponse: " + JSON.stringify(result), "green");
175
362
 
176
- setTimeout(() => {
177
- print("Get Current Subscription Filter Status")
178
- stream.fltGetNews((err) => {
179
- if (err) {
180
- print("err: " + err, "red")
181
- }
182
- })
183
- }, 10000);
184
- }
363
+ // Query filter status again (should show filter 2 + new filter)
364
+ print("Get Current Subscription Filter Status");
365
+ stream.fltGetNews((err) => {
366
+ if (err) {
367
+ print("err: " + err, "red");
368
+ }
369
+ });
370
+ });
371
+ });
372
+ }, 13000);
185
373
 
374
+ // Step 11: Close stream
186
375
  setTimeout(() => {
187
- // Finally, close the stream.
188
376
  stream.close(handleResult(function() {
189
- print("Connection closed")
377
+ print("Connection closed");
190
378
  }));
191
- }, 12000);
192
-
379
+ }, 17000);
193
380
  });
194
381
 
195
382
  }));
@@ -1,7 +1,7 @@
1
1
  <html>
2
2
 
3
3
  <head>
4
- <script src="qmci-streamer-2.64.0.min.js"></script>
4
+ <script src="qmci-streamer-2.66.0.min.js"></script>
5
5
  </head>
6
6
 
7
7
  <body>
@@ -1,7 +1,7 @@
1
1
  <html>
2
2
 
3
3
  <head>
4
- <script src="qmci-streamer-2.64.0.min.js"></script>
4
+ <script src="qmci-streamer-2.66.0.min.js"></script>
5
5
  </head>
6
6
 
7
7
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quotemedia.com/streamer",
3
- "version": "2.64.0",
3
+ "version": "2.66.0",
4
4
  "description": "A JavaScript client for QuoteMedia's streaming data service.",
5
5
  "main": "lib/index.js",
6
6
  "author": "QuoteMedia",