Skip to content

Commit 90c7927

Browse files
authored
feat: add a control for querying store in dogfooding app (#142)
1 parent 07c9a6a commit 90c7927

File tree

4 files changed

+248
-1
lines changed

4 files changed

+248
-1
lines changed

examples/dogfooding/index.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,17 @@ <h2>Controls</h2>
5151
</div>
5252
</section>
5353

54+
<section class="store-query">
55+
<h2>Store Protocol Query</h2>
56+
<div class="store-controls">
57+
<label for="storeMessageCount">Number of messages to fetch:</label>
58+
<input type="number" id="storeMessageCount" value="5" min="1" max="100" />
59+
<button id="queryStoreButton" class="btn btn-primary">Query Store</button>
60+
</div>
61+
<div id="storeQueryStatus" class="store-status"></div>
62+
<div id="storeQueryResults" class="store-results"></div>
63+
</section>
64+
5465
<section class="charts">
5566
<h2>Discovery Peers Over Time</h2>
5667
<canvas id="discoveryChart"></canvas>

examples/dogfooding/public/style.css

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,24 @@ h2 {
9292
background-color: #2980b9;
9393
}
9494

95+
.btn-success {
96+
background-color: #27ae60;
97+
color: white;
98+
}
99+
100+
.btn-success:hover {
101+
background-color: #229954;
102+
}
103+
104+
.btn-danger {
105+
background-color: #e74c3c;
106+
color: white;
107+
}
108+
109+
.btn-danger:hover {
110+
background-color: #c0392b;
111+
}
112+
95113
.search-container {
96114
margin-top: 15px;
97115
display: flex;
@@ -106,6 +124,40 @@ h2 {
106124
font-size: 0.9em;
107125
}
108126

127+
/* Store Query Section */
128+
.store-controls {
129+
display: flex;
130+
align-items: center;
131+
gap: 10px;
132+
flex-wrap: wrap;
133+
}
134+
135+
.store-controls label {
136+
font-weight: 500;
137+
color: #555;
138+
}
139+
140+
.store-controls input[type="number"] {
141+
width: 80px;
142+
padding: 8px;
143+
border: 1px solid #ddd;
144+
border-radius: 5px;
145+
font-size: 0.9em;
146+
}
147+
148+
.store-status {
149+
margin-top: 15px;
150+
padding: 10px;
151+
border-radius: 5px;
152+
min-height: 20px;
153+
}
154+
155+
.store-results {
156+
margin-top: 15px;
157+
max-height: 500px;
158+
overflow-y: auto;
159+
}
160+
109161
/* Message Display */
110162
.message-list {
111163
max-height: 400px;
@@ -214,7 +266,7 @@ footer {
214266
grid-template-columns: repeat(2, 1fr);
215267
}
216268

217-
.message-stats, .message-controls {
269+
.message-stats, .message-controls, .store-query, .charts {
218270
grid-column: span 1;
219271
}
220272

examples/dogfooding/src/index.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ import {
2727
trackMessageSent,
2828
trackMessageReceived,
2929
recordLatency,
30+
updateStoreQueryStatus,
31+
displayStoreQueryResults,
32+
clearStoreQueryResults,
3033
} from "./ui-manager";
3134

3235
const NUM_MESSAGES_PER_BATCH = 5;
@@ -206,6 +209,100 @@ async function initializeApp() {
206209
console.log("Subscription active.");
207210
};
208211

212+
const queryStoreMessages = async () => {
213+
const storeMessageCountInput = document.getElementById("storeMessageCount") as HTMLInputElement;
214+
const messageLimit = storeMessageCountInput ? parseInt(storeMessageCountInput.value, 10) : 5;
215+
216+
if (isNaN(messageLimit) || messageLimit < 1) {
217+
updateStoreQueryStatus("Please enter a valid number of messages (minimum 1)", true);
218+
return;
219+
}
220+
221+
clearStoreQueryResults();
222+
updateStoreQueryStatus("Querying store...", false);
223+
console.log(`Querying store for up to ${messageLimit} messages...`);
224+
225+
try {
226+
const decoder = createWakuDecoder();
227+
const allMessages: ChatMessage[] = [];
228+
229+
console.log("Decoder content topic:", decoder.contentTopic);
230+
console.log("Decoder pubsub topic:", decoder.pubsubTopic);
231+
232+
// Query for messages from the last hour, using paginationLimit to control result size
233+
const timeEnd = new Date();
234+
const timeStart = new Date(Date.now() - 1000 * 60 * 60);
235+
236+
const queryOptions = {
237+
timeStart,
238+
timeEnd,
239+
paginationForward: false, // Start from newest
240+
paginationLimit: messageLimit, // Limit the number of messages returned
241+
};
242+
243+
console.log("Store query options:", queryOptions);
244+
console.log("Time range:", timeStart.toISOString(), "to", timeEnd.toISOString());
245+
246+
// Collect messages - stop once we have enough
247+
await node.store.queryWithOrderedCallback(
248+
[decoder],
249+
async (wakuMessage) => {
250+
// Check if we already have enough messages before processing more
251+
if (allMessages.length >= messageLimit) {
252+
console.log(`Already collected ${messageLimit} messages, stopping`);
253+
return true; // Stop processing
254+
}
255+
256+
const chatMessage = decodeMessage(wakuMessage.payload);
257+
if (chatMessage) {
258+
allMessages.push(chatMessage);
259+
console.log(`Store found message ${allMessages.length}/${messageLimit}:`, {
260+
id: chatMessage.id,
261+
content: chatMessage.content.substring(0, 50),
262+
timestamp: new Date(chatMessage.timestamp).toISOString(),
263+
sender: chatMessage.senderPeerId.substring(0, 12)
264+
});
265+
266+
// Stop if we've reached the limit
267+
if (allMessages.length >= messageLimit) {
268+
console.log(`Reached limit of ${messageLimit} messages, stopping`);
269+
return true; // Stop processing
270+
}
271+
} else {
272+
console.warn("Failed to decode message from store");
273+
}
274+
275+
return false; // Continue to next message
276+
},
277+
queryOptions
278+
);
279+
280+
console.log(`Store query completed. Collected ${allMessages.length} messages.`);
281+
282+
if (allMessages.length > 0) {
283+
// Sort by timestamp descending (newest first)
284+
// Since we're querying with paginationForward: false, we're getting recent messages,
285+
// but they may not be in perfect order, so we sort them
286+
allMessages.sort((a, b) => b.timestamp - a.timestamp);
287+
288+
console.log(`Returning ${allMessages.length} message(s)`);
289+
console.log("Newest message timestamp:", new Date(allMessages[0].timestamp).toISOString());
290+
if (allMessages.length > 1) {
291+
console.log("Oldest returned message timestamp:", new Date(allMessages[allMessages.length - 1].timestamp).toISOString());
292+
}
293+
294+
updateStoreQueryStatus(`✓ Successfully retrieved ${allMessages.length} message${allMessages.length !== 1 ? 's' : ''} from store`, false);
295+
displayStoreQueryResults(allMessages);
296+
} else {
297+
updateStoreQueryStatus("✓ Query completed successfully, but no messages found in store", false);
298+
displayStoreQueryResults([]);
299+
}
300+
} catch (error) {
301+
console.error("Error querying store:", error);
302+
updateStoreQueryStatus(`✗ Error querying store: ${error instanceof Error ? error.message : String(error)}`, true);
303+
}
304+
};
305+
209306
const sendMessageButton = document.getElementById("sendMessageButton");
210307
if (sendMessageButton) {
211308
sendMessageButton.addEventListener("click", () => {
@@ -241,6 +338,14 @@ async function initializeApp() {
241338
});
242339
}
243340

341+
const queryStoreButton = document.getElementById("queryStoreButton");
342+
if (queryStoreButton) {
343+
queryStoreButton.addEventListener("click", () => {
344+
console.log("Query Store button clicked");
345+
queryStoreMessages();
346+
});
347+
}
348+
244349
initCharts();
245350
(window as any).onDiscoveryUpdate = onDiscoveryUpdate;
246351
(window as any).onConnectionsUpdate = onConnectionsUpdate;

examples/dogfooding/src/ui-manager.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,3 +322,82 @@ export function recordLatency(id: string, sent: number, received?: number) {
322322
export function wireUiToggles() {
323323
setupCollapsibles();
324324
}
325+
326+
// Store Query UI
327+
const storeQueryStatusEl = document.getElementById("storeQueryStatus") as HTMLDivElement | null;
328+
const storeQueryResultsEl = document.getElementById("storeQueryResults") as HTMLDivElement | null;
329+
330+
export function updateStoreQueryStatus(message: string, isError: boolean = false) {
331+
if (!storeQueryStatusEl) return;
332+
storeQueryStatusEl.textContent = message;
333+
storeQueryStatusEl.style.color = isError ? '#d32f2f' : '#2e7d32';
334+
storeQueryStatusEl.style.fontWeight = 'bold';
335+
storeQueryStatusEl.style.marginTop = '10px';
336+
}
337+
338+
export function displayStoreQueryResults(messages: ChatMessage[]) {
339+
if (!storeQueryResultsEl) return;
340+
341+
storeQueryResultsEl.innerHTML = "";
342+
343+
if (messages.length === 0) {
344+
storeQueryResultsEl.innerHTML = "<p style='color: #666; font-style: italic;'>No messages found.</p>";
345+
return;
346+
}
347+
348+
const title = document.createElement("h3");
349+
title.textContent = `Found ${messages.length} message${messages.length !== 1 ? 's' : ''}:`;
350+
title.style.marginTop = "15px";
351+
storeQueryResultsEl.appendChild(title);
352+
353+
messages.forEach((message, index) => {
354+
const item = document.createElement("div");
355+
item.classList.add("message-item");
356+
item.style.marginBottom = "10px";
357+
item.style.padding = "10px";
358+
item.style.border = "1px solid #ddd";
359+
item.style.borderRadius = "4px";
360+
item.style.backgroundColor = "#f9f9f9";
361+
362+
const indexLabel = document.createElement("p");
363+
indexLabel.style.fontWeight = "bold";
364+
indexLabel.style.marginBottom = "5px";
365+
indexLabel.textContent = `Message ${index + 1}`;
366+
367+
const idText = document.createElement("p");
368+
idText.style.fontSize = "0.9em";
369+
idText.style.color = "#666";
370+
idText.textContent = `ID: ${message.id}`;
371+
372+
const contentP = document.createElement("p");
373+
contentP.style.margin = "8px 0";
374+
contentP.textContent = message.content;
375+
376+
const senderInfoP = document.createElement("p");
377+
senderInfoP.style.fontSize = "0.9em";
378+
senderInfoP.style.color = "#666";
379+
senderInfoP.textContent = `From: ${message.senderPeerId.substring(0, 12)}...`;
380+
381+
const timestampP = document.createElement("p");
382+
timestampP.style.fontSize = "0.9em";
383+
timestampP.style.color = "#666";
384+
timestampP.textContent = `Time: ${new Date(message.timestamp).toLocaleString()}`;
385+
386+
item.appendChild(indexLabel);
387+
item.appendChild(idText);
388+
item.appendChild(contentP);
389+
item.appendChild(senderInfoP);
390+
item.appendChild(timestampP);
391+
392+
storeQueryResultsEl.appendChild(item);
393+
});
394+
}
395+
396+
export function clearStoreQueryResults() {
397+
if (storeQueryResultsEl) {
398+
storeQueryResultsEl.innerHTML = "";
399+
}
400+
if (storeQueryStatusEl) {
401+
storeQueryStatusEl.textContent = "";
402+
}
403+
}

0 commit comments

Comments
 (0)