@quotemedia.com/streamer 2.64.0 → 2.67.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.
- package/README.md +136 -33
- package/examples/enduser-example.html +1 -1
- package/examples/enterprise-token-example.html +1 -1
- package/examples/oauth-token-example.html +1 -1
- package/examples/reconnect-example.html +1 -1
- package/examples/stomp-3rd-party-library-example.html +1 -1
- package/examples/streaming-news-example.html +354 -166
- package/examples/subscription-example.html +1 -1
- package/examples/wmid-example.html +1 -1
- package/package.json +1 -1
- package/{qmci-streamer-2.64.0.js → qmci-streamer-2.67.0.js} +594 -234
- package/{qmci-streamer-2.64.0.min.js → qmci-streamer-2.67.0.min.js} +8 -8
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<html>
|
|
2
2
|
|
|
3
3
|
<head>
|
|
4
|
-
<script src="qmci-streamer-2.
|
|
4
|
+
<script src="qmci-streamer-2.67.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
|
-
* -
|
|
15
|
-
* -
|
|
16
|
-
* -
|
|
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,196 +31,376 @@
|
|
|
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:
|
|
27
|
-
* Step
|
|
28
|
-
* Step
|
|
29
|
-
* Step
|
|
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 sequentially (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
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
// --- Promise wrappers for callback-based Streamer APIs ---
|
|
46
|
+
function loginAsync(opts) {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
Streamer.login(opts, (err, result) => err ? reject(err) : resolve(result));
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function openAsync(opts) {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
Streamer.open(opts, (err, result) => err ? reject(err) : resolve(result));
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function openNewsAsync(stream, newsClientId) {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
stream.openNews(newsClientId, (err, result) => err ? reject(err) : resolve(result));
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function subscribeNewsAsync(stream, filter, filterId, skipHeavy, newsClientId) {
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
stream.subscribeNews(filter, filterId, skipHeavy, newsClientId, (err, result) => err ? reject(err) : resolve(result));
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function fltGetNewsAsync(stream) {
|
|
71
|
+
return new Promise((resolve) => {
|
|
72
|
+
let done = false;
|
|
73
|
+
stream.on("newsRemoteMessage", function(msg) {
|
|
74
|
+
if (!done && msg["@T"] === "C37") {
|
|
75
|
+
done = true;
|
|
76
|
+
// resolve on next tick so the existing newsRemoteMessage handler prints first
|
|
77
|
+
setTimeout(() => resolve(msg), 0);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
stream.fltGetNews((err) => {
|
|
81
|
+
if (err && !done) {
|
|
82
|
+
done = true;
|
|
83
|
+
print("err: " + err, "red");
|
|
84
|
+
resolve();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function fltUpdateNewsAsync(stream, filter, filterId) {
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
stream.fltUpdateNews(filter, filterId, (err, result) => err ? reject(err) : resolve(result));
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function unsubscribeNewsAsync(stream, filterId) {
|
|
97
|
+
return new Promise((resolve) => {
|
|
98
|
+
let done = false;
|
|
99
|
+
stream.on("filter delete", function(msg) {
|
|
100
|
+
if (!done) {
|
|
101
|
+
done = true;
|
|
102
|
+
resolve(msg);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
stream.unsubscribeNews(filterId, (err) => {
|
|
106
|
+
if (err && !done) {
|
|
107
|
+
done = true;
|
|
108
|
+
print("Failed to unsubscribe News filter: " + err, "red");
|
|
109
|
+
resolve();
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function closeAsync(stream) {
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
stream.close((err, result) => err ? reject(err) : resolve(result));
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// --- Main async flow ---
|
|
122
|
+
run().catch(err => print("Unexpected error: " + err, "red"));
|
|
123
|
+
|
|
124
|
+
async function run() {
|
|
125
|
+
// Step 1: Log in to get an SID
|
|
126
|
+
const sid = await loginAsync({
|
|
127
|
+
host: 'https://app.quotemedia.com/auth',
|
|
128
|
+
credentials: {
|
|
129
|
+
wmid: "YourWebmasterID",
|
|
130
|
+
username: "YourUsername",
|
|
131
|
+
password: "YourPassword"
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Step 2: Open the streaming connection
|
|
136
|
+
const stream = await openAsync({
|
|
43
137
|
host: 'https://app.quotemedia.com/cache',
|
|
44
138
|
cors: true,
|
|
45
139
|
rejectExcessiveConnection: false,
|
|
46
140
|
conflation: null,
|
|
47
141
|
format: 'application/json',
|
|
48
142
|
credentials: { sid: sid }
|
|
49
|
-
}
|
|
50
|
-
// After successfully opening the stream,
|
|
51
|
-
// listen for its events.
|
|
52
|
-
stream
|
|
53
|
-
// The stream will asynchronously callback with
|
|
54
|
-
// incoming market data messages.
|
|
55
|
-
.on("message", function(message) {
|
|
56
|
-
print(msgFmt.fmt(message), "dodgerblue")
|
|
57
|
-
})
|
|
58
|
-
// It's recommended to attach an error handler
|
|
59
|
-
// to help diagnose unexpected errors.
|
|
60
|
-
.on("error", function(err) {
|
|
61
|
-
print(err, "red");
|
|
62
|
-
}).on("close", function(msg) {
|
|
63
|
-
print("Closed: " + msg);
|
|
64
|
-
// To catch and handling the News messages
|
|
65
|
-
}).on("newsRemoteMessage", function(msg) {
|
|
66
|
-
print("newsRemoteMessage: " + msgFmt.fmt(msg), "green");
|
|
67
|
-
});
|
|
143
|
+
});
|
|
68
144
|
|
|
145
|
+
// Step 3: Set up event listeners
|
|
146
|
+
stream
|
|
147
|
+
.on("message", function(message) {
|
|
148
|
+
/**
|
|
149
|
+
* # News Data Message Fields (type "D18").
|
|
150
|
+
*
|
|
151
|
+
* Available fields on the message object:
|
|
152
|
+
* headline, summary, source, sourceId, symbol,
|
|
153
|
+
* storyId, storyUrl, newsUrl, timestamp, epochtime,
|
|
154
|
+
* lang, excode, exgroup, topic, sector,
|
|
155
|
+
* thumbnailUrl, videoUrl, videoImageUrl,
|
|
156
|
+
* vendorDateId, filterId
|
|
157
|
+
*/
|
|
158
|
+
if (message["@T"] === "D18") {
|
|
159
|
+
// Display rich News data fields
|
|
160
|
+
print("--- News Article Received ---", "dodgerblue");
|
|
161
|
+
print(" Headline: " + message.headline, "dodgerblue");
|
|
162
|
+
print(" Source: " + message.source + " (" + message.sourceId + ")", "dodgerblue");
|
|
163
|
+
print(" Symbol: " + message.symbol, "dodgerblue");
|
|
164
|
+
print(" Topic: " + message.topic, "dodgerblue");
|
|
165
|
+
print(" Sector: " + message.sector, "dodgerblue");
|
|
166
|
+
print(" Time: " + new Date(message.timestamp).toLocaleString(), "dodgerblue");
|
|
167
|
+
print(" FilterId: " + message.filterId, "dodgerblue");
|
|
168
|
+
if (message.summary) {
|
|
169
|
+
print(" Summary: " + message.summary.substring(0, 200) + "...", "dodgerblue");
|
|
170
|
+
}
|
|
171
|
+
if (message.storyUrl) {
|
|
172
|
+
print(" Story URL: " + message.storyUrl, "dodgerblue");
|
|
173
|
+
}
|
|
174
|
+
if (message.thumbnailUrl) {
|
|
175
|
+
print(" Thumbnail: " + message.thumbnailUrl, "dodgerblue");
|
|
176
|
+
}
|
|
177
|
+
if (message.videoUrl) {
|
|
178
|
+
print(" Video: " + message.videoUrl, "dodgerblue");
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
print(msgFmt.fmt(message), "dodgerblue");
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
.on("error", function(err) {
|
|
185
|
+
print(err, "red");
|
|
186
|
+
}).on("close", function(msg) {
|
|
187
|
+
print("Closed: " + msg);
|
|
188
|
+
})
|
|
69
189
|
/**
|
|
70
|
-
*
|
|
190
|
+
* # Differentiate News Message Types.
|
|
71
191
|
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* @param exgroup Array of exchange group identifiers.
|
|
192
|
+
* The "newsRemoteMessage" event can carry different message types:
|
|
193
|
+
* - "C37" (NEWS_FILTER_MESSAGE): Normal news filter notification
|
|
194
|
+
* - "C38" (NEWS_ERROR_MESSAGE): Error notification with event, code, message fields
|
|
195
|
+
* - "C39" (NEWS_SUCCESS_MESSAGE): Success notification with code, message fields
|
|
77
196
|
*
|
|
78
|
-
*
|
|
197
|
+
* Use the "@T" field to differentiate and handle each type accordingly.
|
|
79
198
|
*/
|
|
199
|
+
.on("newsRemoteMessage", function(msg) {
|
|
200
|
+
switch (msg["@T"]) {
|
|
201
|
+
case "C37": // NEWS_FILTER_MESSAGE
|
|
202
|
+
print("[NEWS FILTER] " + msg.message, "green");
|
|
203
|
+
break;
|
|
204
|
+
case "C38": // NEWS_ERROR_MESSAGE
|
|
205
|
+
print("[NEWS ERROR] Event: " + msg.event + ", Code: " + msg.code + ", Message: " + msg.message, "red");
|
|
206
|
+
break;
|
|
207
|
+
case "C39": // NEWS_SUCCESS_MESSAGE
|
|
208
|
+
print("[NEWS SUCCESS] Code: " + msg.code + ", Message: " + msg.message, "limegreen");
|
|
209
|
+
break;
|
|
210
|
+
default:
|
|
211
|
+
print("newsRemoteMessage: " + msgFmt.fmt(msg), "green");
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
});
|
|
80
215
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Supported filter parameters for Streaming News:
|
|
218
|
+
*
|
|
219
|
+
* Array filters (use association: "OR" or "AND"):
|
|
220
|
+
* @param src Array of news source identifiers (e.g., "djns", "bwi", "mtn").
|
|
221
|
+
* @param topic Array of topic names (e.g., "Market and Economy").
|
|
222
|
+
* @param sector Array of sector codes (e.g., "101", "103").
|
|
223
|
+
* @param symbol Array of stock symbols (e.g., "AAPL", "GOOG", "^DJT").
|
|
224
|
+
* @param excode Array of exchange codes (e.g., "TSX", "NYE").
|
|
225
|
+
* @param exgroup Array of exchange group identifiers (e.g., "NSD", "DOW").
|
|
226
|
+
* @param keyword Array of keyword strings to match in news content (e.g., "analyst", "sell", "exchange offer").
|
|
227
|
+
*
|
|
228
|
+
* Boolean/value parameters (use association: null):
|
|
229
|
+
* @param summary Boolean. If true, include article summary in the response.
|
|
230
|
+
* @param summlen Number. Maximum length of the summary text (e.g., 100).
|
|
231
|
+
* @param constituent Boolean. If true, include constituent-related news.
|
|
232
|
+
* @param searchByExchange Boolean. If true, search news by exchange.
|
|
233
|
+
*/
|
|
96
234
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
]
|
|
235
|
+
/**
|
|
236
|
+
* The Below filter will return the News from
|
|
237
|
+
* "source" bwi OR djns, and contains
|
|
238
|
+
* "topic" Market and Economy, and contains
|
|
239
|
+
* "sector" 101 OR 103, and contains
|
|
240
|
+
* "symbol" AAPL OR GOOG OR ^DJT, and contains
|
|
241
|
+
* "excode" TSX, and contains
|
|
242
|
+
* "exgroup" NSD, and contains
|
|
243
|
+
* "keyword" analyst OR sell OR exchange offer.
|
|
244
|
+
* Additionally, includes summary (max 100 chars),
|
|
245
|
+
* constituent news, and searches by exchange.
|
|
246
|
+
*/
|
|
247
|
+
const newsFilterExampleJson1 = [
|
|
248
|
+
{ name: "src", value: ["bwi", "djns"], association: "OR" },
|
|
249
|
+
{ name: "topic", value: ["Market and Economy"], association: "OR" },
|
|
250
|
+
{ name: "sector", value: ["101", "103"], association: "OR" },
|
|
251
|
+
{ name: "symbol", value: ["AAPL", "GOOG", "^DJT"], association: "OR" },
|
|
252
|
+
{ name: "excode", value: ["TSX"], association: "OR" },
|
|
253
|
+
{ name: "exgroup", value: ["NSD"], association: "OR" },
|
|
254
|
+
{ name: "keyword", value: ["analyst", "sell", "exchange offer"], association: "OR" },
|
|
255
|
+
{ name: "summary", value: true, association: null },
|
|
256
|
+
{ name: "summlen", value: 100, association: null },
|
|
257
|
+
{ name: "constituent", value: true, association: null },
|
|
258
|
+
{ name: "searchByExchange", value: true, association: null }
|
|
259
|
+
];
|
|
112
260
|
|
|
113
|
-
|
|
114
|
-
|
|
261
|
+
/**
|
|
262
|
+
* The Below filter will return the News from
|
|
263
|
+
* "source" djns OR mtn OR bwi, and contains
|
|
264
|
+
* "topic" Market and Economy OR Entertainment, and contains
|
|
265
|
+
* "sector" 101, and contains
|
|
266
|
+
* "symbol" GOOG OR AAPL OR COST, and contains
|
|
267
|
+
* "excode" TSX OR NYE, and contains
|
|
268
|
+
* "exgroup" DOW OR NSD, and contains
|
|
269
|
+
* "keyword" earnings OR merger.
|
|
270
|
+
* Additionally, includes summary (max 200 chars).
|
|
271
|
+
*/
|
|
272
|
+
const newsFilterExampleJson2 = [
|
|
273
|
+
{ name: "src", value: ["djns", "mtn", "bwi"], association: "OR" },
|
|
274
|
+
{ name: "topic", value: ["Market and Economy", "Entertainment"], association: "OR" },
|
|
275
|
+
{ name: "sector", value: ["101"], association: "OR" },
|
|
276
|
+
{ name: "symbol", value: ["GOOG", "AAPL", "COST"], association: "OR" },
|
|
277
|
+
{ name: "excode", value: ["TSX", "NYE"], association: "OR" },
|
|
278
|
+
{ name: "exgroup", value: ["DOW", "NSD"], association: "OR" },
|
|
279
|
+
{ name: "keyword", value: ["earnings", "merger"], association: "OR" },
|
|
280
|
+
{ name: "summary", value: true, association: null },
|
|
281
|
+
{ name: "summlen", value: 200, association: null }
|
|
282
|
+
];
|
|
115
283
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
print("Failed to subscribe News filter", "red")
|
|
126
|
-
} else {
|
|
127
|
-
print("News Connection opened")
|
|
128
|
-
print("SubscribeResponse: " + JSON.stringify(result), "green");
|
|
129
|
-
|
|
130
|
-
setTimeout(() => {
|
|
131
|
-
print("Get Current Subscription Filter Status")
|
|
132
|
-
stream.fltGetNews((err) => {
|
|
133
|
-
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");
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
}, 4000)
|
|
157
|
-
|
|
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);
|
|
166
|
-
|
|
167
|
-
setTimeout(() => {
|
|
168
|
-
print("Unsubscribe News Filter")
|
|
169
|
-
stream.fltDeleteNews(filterId, (err) => {
|
|
170
|
-
if (err) {
|
|
171
|
-
print("err: " + err, "red")
|
|
172
|
-
}
|
|
173
|
-
})
|
|
174
|
-
}, 8000);
|
|
175
|
-
|
|
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
|
-
}
|
|
284
|
+
/**
|
|
285
|
+
* A minimal filter targeting only source and symbols,
|
|
286
|
+
* used to demonstrate multiple concurrent filter subscriptions.
|
|
287
|
+
* Not all parameters are required — you can use as few as needed.
|
|
288
|
+
*/
|
|
289
|
+
const newsFilterExampleJson3 = [
|
|
290
|
+
{ name: "src", value: ["djns"], association: "OR" },
|
|
291
|
+
{ name: "symbol", value: ["MSFT", "AMZN", "TSLA"], association: "OR" }
|
|
292
|
+
];
|
|
185
293
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}));
|
|
191
|
-
}, 12000);
|
|
294
|
+
const filterId1 = crypto.randomUUID();
|
|
295
|
+
const filterId2 = crypto.randomUUID();
|
|
296
|
+
print("News FilterId 1: " + filterId1, null, true);
|
|
297
|
+
print("News FilterId 2: " + filterId2, null, true);
|
|
192
298
|
|
|
193
|
-
|
|
299
|
+
/**
|
|
300
|
+
* # Step 4: Open a News connection.
|
|
301
|
+
*
|
|
302
|
+
* openNews() establishes a News connection and returns a newsClientId.
|
|
303
|
+
* - newsClientId: Can be null for a new connection, or pass an existing
|
|
304
|
+
* newsClientId to reconnect to a previous News session.
|
|
305
|
+
* - The returned newsClientId should be saved for potential reconnection.
|
|
306
|
+
*/
|
|
307
|
+
const openResult = await openNewsAsync(stream, null);
|
|
308
|
+
const newsClientId = openResult.newsClientId;
|
|
309
|
+
print("News connection opened, newsClientId: " + newsClientId, "green", true);
|
|
194
310
|
|
|
195
|
-
|
|
311
|
+
/**
|
|
312
|
+
* # Step 5: Subscribe to the first News Filter.
|
|
313
|
+
*
|
|
314
|
+
* skipHeavyInitialLoad: false
|
|
315
|
+
* - The server will skip sending buffered/initial news data.
|
|
316
|
+
* - Only new incoming news after subscription will be delivered.
|
|
317
|
+
* - Note: Setting this to false will cause the server to send all
|
|
318
|
+
* buffered/initial news data upon subscription, which may significantly
|
|
319
|
+
* increase the subscription response time.
|
|
320
|
+
*
|
|
321
|
+
* skipHeavyInitialLoad: true
|
|
322
|
+
* - The server will skip sending buffered/initial news data.
|
|
323
|
+
* - Only new incoming news after subscription will be delivered.
|
|
324
|
+
* - This reduces initial bandwidth and is recommended when historical data is not needed.
|
|
325
|
+
*/
|
|
326
|
+
const subResult1 = await subscribeNewsAsync(stream, newsFilterExampleJson1, filterId1, true, newsClientId);
|
|
327
|
+
print("News filter 1 subscribed (skipHeavyInitialLoad: true)", null, true);
|
|
328
|
+
print("SubscribeResponse 1: " + JSON.stringify(subResult1), "green");
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* # Step 6: Subscribe a second filter with a different filterId.
|
|
332
|
+
*
|
|
333
|
+
* Multiple filters can be active simultaneously, each identified by a unique filterId.
|
|
334
|
+
* Each filter operates independently and can be updated or removed separately.
|
|
335
|
+
*/
|
|
336
|
+
const subResult2 = await subscribeNewsAsync(stream, newsFilterExampleJson3, filterId2, true, newsClientId);
|
|
337
|
+
print("News filter 2 subscribed (skipHeavyInitialLoad: true)", null, true);
|
|
338
|
+
print("SubscribeResponse 2: " + JSON.stringify(subResult2), "green");
|
|
339
|
+
|
|
340
|
+
// Step 7: Query current News Filter status (should show both filters)
|
|
341
|
+
print("Get Current Subscription Filter Status", null, true);
|
|
342
|
+
await fltGetNewsAsync(stream);
|
|
343
|
+
|
|
344
|
+
// Step 8: Update filter 1 with new criteria
|
|
345
|
+
print("Update News Filter 1", null, true);
|
|
346
|
+
try {
|
|
347
|
+
const updateResult = await fltUpdateNewsAsync(stream, newsFilterExampleJson2, filterId1);
|
|
348
|
+
print("News filter 1 updated", null, true);
|
|
349
|
+
print("Filter Update Response: " + JSON.stringify(updateResult), "green");
|
|
350
|
+
} catch (e) {
|
|
351
|
+
print("Failed to update News filter: " + e, "red", true);
|
|
352
|
+
}
|
|
196
353
|
|
|
197
|
-
|
|
354
|
+
// Query filter status after update
|
|
355
|
+
print("Get Current Subscription Filter Status", null, true);
|
|
356
|
+
await fltGetNewsAsync(stream);
|
|
198
357
|
|
|
199
|
-
|
|
358
|
+
// Step 9: Unsubscribe from filter 1
|
|
359
|
+
print("Unsubscribe News Filter 1", null, true);
|
|
360
|
+
await unsubscribeNewsAsync(stream, filterId1);
|
|
361
|
+
print("News filter 1 unsubscribed", null, true);
|
|
362
|
+
|
|
363
|
+
// Query filter status after unsubscribe (filter 2 should still be active)
|
|
364
|
+
print("Get Current Subscription Filter Status", null, true);
|
|
365
|
+
await fltGetNewsAsync(stream);
|
|
366
|
+
|
|
367
|
+
// Step 10: Demonstrate News reconnection with existing newsClientId
|
|
368
|
+
print("--- Reconnecting News with existing newsClientId ---", "orange", true);
|
|
369
|
+
const reconnectResult = await openNewsAsync(stream, newsClientId);
|
|
370
|
+
print("News reconnected, newsClientId: " + reconnectResult.newsClientId, null, true);
|
|
371
|
+
|
|
372
|
+
// Query filter status after reconnect (filter 2 should still be active)
|
|
373
|
+
print("Get Current Subscription Filter Status", null, true);
|
|
374
|
+
await fltGetNewsAsync(stream);
|
|
375
|
+
|
|
376
|
+
// Subscribe a new filter after reconnect
|
|
377
|
+
const reconnectFilterId = crypto.randomUUID();
|
|
378
|
+
print("Reconnect FilterId: " + reconnectFilterId, "green");
|
|
379
|
+
|
|
380
|
+
const reconnectSubResult = await subscribeNewsAsync(stream, newsFilterExampleJson1, reconnectFilterId, true, reconnectResult.newsClientId);
|
|
381
|
+
print("News filter subscribed after reconnect", null, true);
|
|
382
|
+
print("ReconnectSubscribeResponse: " + JSON.stringify(reconnectSubResult), "green");
|
|
383
|
+
|
|
384
|
+
// Query filter status again (should show filter 2 + new filter)
|
|
385
|
+
print("Get Current Subscription Filter Status", null, true);
|
|
386
|
+
await fltGetNewsAsync(stream);
|
|
387
|
+
|
|
388
|
+
// Step 11: Close stream
|
|
389
|
+
await closeAsync(stream);
|
|
390
|
+
print("Connection closed", null, true);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function print(msg, color, bold) {
|
|
200
394
|
var el = document.createElement("div");
|
|
201
395
|
el.innerText = msg;
|
|
202
396
|
if (color) {
|
|
203
397
|
el.style.color = color;
|
|
204
398
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
function handleResult(onSuccess) {
|
|
209
|
-
return function(err, result) {
|
|
210
|
-
if (err) {
|
|
211
|
-
print(err, "red");
|
|
212
|
-
} else {
|
|
213
|
-
onSuccess(result);
|
|
214
|
-
}
|
|
399
|
+
if (bold) {
|
|
400
|
+
el.style.fontWeight = "bold";
|
|
401
|
+
el.style.fontSize = "1.15em";
|
|
215
402
|
}
|
|
403
|
+
document.body.appendChild(el);
|
|
216
404
|
}
|
|
217
405
|
};
|
|
218
406
|
</script>
|