Skip to content

Add TypeScript web demo using compiled Closure bundle#2

Open
sampajano wants to merge 6 commits into
mainfrom
webchannel-js-demo
Open

Add TypeScript web demo using compiled Closure bundle#2
sampajano wants to merge 6 commits into
mainfrom
webchannel-js-demo

Conversation

@sampajano

Copy link
Copy Markdown
Owner

Integrated the newly built ES2022 CJS module blob into the local TS web demo, verifying end-to-end streaming against the MessageGenerator.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the WebChannel multi-language client directory, featuring a JavaScript/TypeScript client based on the Google Closure Library, complete with type definitions, unit tests, and a local web demo. Feedback on the demo code highlights a high-severity directory traversal vulnerability in the Node.js server (server.js) and a connection leak along with potential runtime TypeErrors in the client script (demo.ts). Code suggestions are provided to secure the server path resolution and properly manage active channel connections.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread webchannel/js/demo/server.js Outdated
Comment on lines +8 to +16
// Prevent directory traversal attacks
let safeUrl = req.url.split('?')[0];
let filePath = path.join(__dirname, safeUrl === '/' ? 'index.html' : safeUrl);

if (!filePath.startsWith(__dirname)) {
res.writeHead(403);
res.end('Forbidden');
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The path resolution logic is vulnerable to directory traversal attacks. Checking if filePath starts with __dirname is insufficient because a path like /app/webchannel/js/demo-sibling/file.js also starts with /app/webchannel/js/demo if __dirname is /app/webchannel/js/demo. Additionally, the URL should be decoded using decodeURIComponent before resolving the path to handle URL-encoded traversal sequences correctly.

To fix this, decode the URL and use path.relative to verify that the resolved path is strictly within the target directory.

Suggested change
// Prevent directory traversal attacks
let safeUrl = req.url.split('?')[0];
let filePath = path.join(__dirname, safeUrl === '/' ? 'index.html' : safeUrl);
if (!filePath.startsWith(__dirname)) {
res.writeHead(403);
res.end('Forbidden');
return;
}
// Prevent directory traversal attacks and decode URL components
const decodedUrl = decodeURIComponent(req.url.split('?')[0]);
const filePath = path.join(__dirname, decodedUrl === '/' ? 'index.html' : decodedUrl);
const relative = path.relative(__dirname, filePath);
if (relative.startsWith('..') || path.isAbsolute(relative)) {
res.writeHead(403);
res.end('Forbidden');
return;
}

Comment on lines +19 to +67

sendButton.addEventListener('click', () => {
const text = messageInput.value || 'Hello from WebChannel TS Demo!';
log(`>>> Sending request with message: "${text}"`);

const options = {
supportsCrossDomainXhr: true,
httpSessionIdParam: 'gsessionid'
};

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

channel.listen(WebChannel.EventType.OPEN, () => {
log('>>> WebChannel opened!');

const payload = {
message: text,
num_messages: 5,
message_interval: 1000
};

channel.send(payload);
log(`>>> Sent request payload: ${JSON.stringify(payload)}`);
});

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

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)}`);
});

channel.listen(WebChannel.EventType.CLOSE, () => {
log('<<< WebChannel closed!');
});

channel.open();
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Every time the 'Send Streaming Echo' button is clicked, a new WebChannel connection is created and opened without closing the previous one. This leads to connection leaks and duplicate event listeners logging to the same UI element. Additionally, accessing sendButton and messageInput without null checks can throw runtime TypeErrors if the DOM elements are missing.

Use optional chaining and track the active channel to close any existing connection before opening a new one.

let activeChannel: any = null;

sendButton?.addEventListener('click', () => {
  if (activeChannel) {
    log('>>> Closing previous active WebChannel connection...');
    activeChannel.close();
  }
  const text = messageInput?.value || 'Hello from WebChannel TS Demo!';
  log('>>> Sending request with message: "' + text + '"');
  
  const options = {
    supportsCrossDomainXhr: true,
    httpSessionIdParam: 'gsessionid'
  };
  
  const channel = channelFactory.createWebChannel(url, options);
  activeChannel = channel;
  
  channel.listen(WebChannel.EventType.OPEN, () => {
    log('>>> WebChannel opened!');
    
    const payload = {
      message: text,
      num_messages: 5,
      message_interval: 1000
    };
    
    channel.send(payload);
    log('>>> Sent request payload: ' + JSON.stringify(payload));
  });
  
  channel.listen(WebChannel.EventType.MESSAGE, (event: any) => {
    const inbound = event.data;
    log('<<< Received message event. Raw data: ' + JSON.stringify(inbound));
    
    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;
    }
  });
  
  channel.listen(WebChannel.EventType.CLOSE, () => {
    log('<<< WebChannel closed!');
    if (activeChannel === channel) {
      activeChannel = null;
    }
  });
  
  channel.open();
});

@sampajano sampajano force-pushed the webchannel-js-demo branch 3 times, most recently from 90476a2 to d41faaf Compare June 10, 2026 17:44
@sampajano

Copy link
Copy Markdown
Owner Author

I have resolved the review comments:

  1. Directory Traversal Fix: Decoded the request URL and updated server.js to use path.relative to safely resolve files strictly within the demo directory.
  2. Connection Leak & Optional Chaining Fix: Tracked activeChannel in demo.ts and added connection cleanup on subsequent button clicks. Added optional chaining (?.) on DOM elements to prevent runtime TypeErrors.

All changes have been successfully tested and force-pushed to the webchannel-js-demo branch.

@sampajano sampajano force-pushed the webchannel-js-demo branch from d41faaf to 6b4fb2f Compare June 23, 2026 19:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant