Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions webchannel/js/demo/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
scratch/
dist/
node_modules/
184 changes: 184 additions & 0 deletions webchannel/js/demo/demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import { createWebChannelTransport, WebChannel } from '../dist/webchannel_blob_es2022';

const logElement = document.getElementById('log') as HTMLTextAreaElement;
const endpointSelect = document.getElementById('endpoint') as HTMLSelectElement;
const customEndpointInput = document.getElementById('custom-endpoint') as HTMLInputElement;
const sendRawJsonCheckbox = document.getElementById('sendRawJson') as HTMLInputElement;
const detectBufferingProxyCheckbox = document.getElementById('detectBufferingProxy') as HTMLInputElement;
const forceLongPollingCheckbox = document.getElementById('forceLongPolling') as HTMLInputElement;
const fastHandshakeCheckbox = document.getElementById('fastHandshake') as HTMLInputElement;

const connectButton = document.getElementById('connect') as HTMLButtonElement;
const disconnectButton = document.getElementById('disconnect') as HTMLButtonElement;
const sendButton = document.getElementById('send') as HTMLButtonElement;
const messageInput = document.getElementById('message') as HTMLInputElement;
const clearButton = document.getElementById('clear') as HTMLButtonElement;
const statusBadge = document.getElementById('status-badge') as HTMLSpanElement;

function updateStatus(status: 'disconnected' | 'connecting' | 'connected') {
if (!statusBadge) return;
if (status === 'disconnected') {
statusBadge.textContent = 'Disconnected';
statusBadge.style.backgroundColor = '#6c757d'; // Grey
statusBadge.style.color = 'white';
} else if (status === 'connecting') {
statusBadge.textContent = 'Connecting...';
statusBadge.style.backgroundColor = '#ffc107'; // Yellow
statusBadge.style.color = '#333';
} else if (status === 'connected') {
statusBadge.textContent = 'Connected';
statusBadge.style.backgroundColor = '#28a745'; // Green
statusBadge.style.color = 'white';
}
}

function log(msg: string) {
console.log(msg);
if (logElement) {
logElement.value += msg + '\n';
logElement.scrollTop = logElement.scrollHeight;
}
}

log('Initializing WebChannel demo...');

const channelFactory = createWebChannelTransport();
let activeChannel: any = null;
let lastSendTime: number = 0;
let isPendingEcho: boolean = false;

function updateUIState(connected: boolean) {
if (connectButton) connectButton.disabled = connected;
if (disconnectButton) disconnectButton.disabled = !connected;
if (sendButton) sendButton.disabled = !connected;
if (endpointSelect) endpointSelect.disabled = connected;
if (customEndpointInput) customEndpointInput.disabled = connected;

// Disable options toggles while connected
if (sendRawJsonCheckbox) sendRawJsonCheckbox.disabled = connected;
if (detectBufferingProxyCheckbox) detectBufferingProxyCheckbox.disabled = connected;
if (forceLongPollingCheckbox) forceLongPollingCheckbox.disabled = connected;
if (fastHandshakeCheckbox) fastHandshakeCheckbox.disabled = connected;
}

connectButton?.addEventListener('click', () => {
try {
const url = endpointSelect?.value === 'custom'
? (customEndpointInput?.value || '')
: (endpointSelect?.value || 'https://webchannel.sandbox.google.com/staging/channel/generator');

const options: any = {
supportsCrossDomainXhr: true,
httpSessionIdParam: 'gsessionid',
sendRawJson: sendRawJsonCheckbox?.checked,
detectBufferingProxy: detectBufferingProxyCheckbox?.checked,
forceLongPolling: forceLongPollingCheckbox?.checked,
fastHandshake: fastHandshakeCheckbox?.checked
};

log(`>>> Opening WebChannel connection to: ${url}`);
log(`>>> With options: ${JSON.stringify(options)}`);

const channel = channelFactory.createWebChannel(url, options);
activeChannel = channel;

channel.listen(WebChannel.EventType.OPEN, () => {
log('>>> WebChannel connection established!');
updateUIState(true);
updateStatus('connected');
});

channel.listen(WebChannel.EventType.MESSAGE, (event: any) => {
const inbound = event.data;
log(`<<< Received message event. Raw data: ${JSON.stringify(inbound)}`);

if (isPendingEcho) {
const duration = (performance.now() - lastSendTime).toFixed(2);
log(`<<< [E2E Latency] Round-trip echo completed in ${duration} ms.`);
isPendingEcho = false;
}

const result = Array.isArray(inbound) ? inbound[0] : inbound;
if (result) {
if (result.error) {
log(`<<< ERROR from server: ${result.error.message}`);
} else if (result.message) {
log(`<<< Echo response message: "${result.message}"`);
}
}
});

channel.listen(WebChannel.EventType.ERROR, (error: any) => {
log(`<<< WebChannel error: ${JSON.stringify(error)}`);
if (activeChannel === channel) {
activeChannel = null;
updateUIState(false);
updateStatus('disconnected');
}
});

channel.listen(WebChannel.EventType.CLOSE, () => {
log('<<< WebChannel closed!');
if (activeChannel === channel) {
activeChannel = null;
updateUIState(false);
updateStatus('disconnected');
}
});

updateStatus('connecting');
channel.open();
} catch (err: any) {
log(`!!! Exception caught during connect: ${err.message}\n${err.stack}`);
updateStatus('disconnected');
}
});

disconnectButton?.addEventListener('click', () => {
if (activeChannel) {
log('>>> Closing active WebChannel connection...');
activeChannel.close();
}
});

sendButton?.addEventListener('click', () => {
if (!activeChannel) return;

const text = messageInput?.value || 'Hello from WebChannel TS Demo!';
const endpoint = endpointSelect?.value === 'custom'
? (customEndpointInput?.value || '')
: (endpointSelect?.value || '');

let payload: any;
if (endpoint.includes('/staging/channel/generator')) {
payload = {
message: text,
num_messages: 5,
message_interval: 1000
};
log(`>>> Sending request payload to generator: ${JSON.stringify(payload)}`);
} else {
payload = {
message: text
};
log(`>>> Sending request payload to echo: ${JSON.stringify(payload)}`);
}

lastSendTime = performance.now();
isPendingEcho = true;
activeChannel.send(payload);
});

endpointSelect?.addEventListener('change', () => {
if (customEndpointInput) {
customEndpointInput.style.display = endpointSelect.value === 'custom' ? 'inline-block' : 'none';
}
});

clearButton?.addEventListener('click', () => {
if (logElement) {
logElement.value = '';
}
});

log('Initialization completed. Ready.');
104 changes: 104 additions & 0 deletions webchannel/js/demo/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebChannel TypeScript Web Demo</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
background-color: #f5f5f5;
}
h1 {
color: #333;
}
.container {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
max-width: 800px;
}
.input-group {
margin-bottom: 20px;
}
input[type="text"] {
width: 60%;
padding: 8px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
padding: 8px 16px;
font-size: 16px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
button:disabled {
opacity: 0.6;
cursor: not-allowed;
background-color: #6c757d !important;
}
textarea {
width: 100%;
height: 400px;
font-family: monospace;
font-size: 14px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
</style>
</head>
<body>
<div class="container">
<h1>WebChannel TypeScript Web Demo</h1>
<p>This demo uses the compiled Closure WebChannel JS bundle to talk to the local echo server backend.</p>

<div class="input-group" style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">
<label for="endpoint" style="font-weight: bold;">Endpoint:</label>
<select id="endpoint" style="padding: 6px; font-size: 15px; border-radius: 4px; border: 1px solid #ccc;">
<option value="https://webchannel.sandbox.google.com/staging/channel/generator" selected>Generator Server (Streaming)</option>
<option value="https://webchannel.sandbox.google.com/staging/channel/echo">Echo Server (Latency Test)</option>
<option value="custom">Custom Endpoint...</option>
</select>
<input type="text" id="custom-endpoint" value="" placeholder="Enter custom URL..." style="display: none; width: 45%; padding: 6px; font-size: 15px; border-radius: 4px; border: 1px solid #ccc;" />
</div>

<div class="input-group" style="display: flex; gap: 20px; align-items: center; flex-wrap: wrap; margin-bottom: 20px;">
<span style="font-weight: bold;">Options:</span>
<label><input type="checkbox" id="sendRawJson" checked> sendRawJson</label>
<label><input type="checkbox" id="detectBufferingProxy"> detectBufferingProxy</label>
<label><input type="checkbox" id="forceLongPolling"> forceLongPolling</label>
<label><input type="checkbox" id="fastHandshake"> fastHandshake</label>
</div>

<div class="input-group" style="display: flex; gap: 10px; align-items: center;">
<button id="connect" style="background-color: #28a745;">Connect</button>
<button id="disconnect" style="background-color: #dc3545;" disabled>Disconnect</button>
<span id="status-badge" style="font-size: 14px; font-weight: bold; padding: 4px 8px; border-radius: 12px; margin-left: 10px; background-color: #6c757d; color: white;">Disconnected</span>
</div>

<div class="input-group">
<input type="text" id="message" value="Hello WebChannel!" placeholder="Enter message to send..." />
<button id="send" disabled>Send Message</button>
</div>

<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; margin-top: 20px;">
<h3 style="margin: 0;">Connection Logs</h3>
<button id="clear" style="background-color: #6c757d; padding: 4px 8px; font-size: 13px;">Clear Logs</button>
</div>
<textarea id="log" readonly></textarea>
</div>

<script src="dist/demo.js"></script>
</body>
</html>
Loading