Explorar o código

include files for TCP socket type (ripped from google examples) and export types folder in svelte.conf so it's usable

oscar hai 2 meses
pai
achega
6a57003ef3
Modificáronse 9 ficheiros con 573 adicións e 23 borrados
  1. 0 1
      cxcore.js
  2. 9 22
      post_process.cjs
  3. 3 0
      src/app.d.js
  4. 183 0
      src/lib/socket-connection.ts
  5. 69 0
      src/lib/socket-log.ts
  6. 141 0
      src/lib/socket-server.ts
  7. 85 0
      src/lib/streams.ts
  8. 3 0
      svelte.config.js
  9. 80 0
      types/directSockets.d.ts

+ 0 - 1
cxcore.js

@@ -9659,4 +9659,3 @@ function assignHeaps(tmp0){
 	HEAPF32=new Float32Array(tmp0);
 	HEAPF64=new Float64Array(tmp0);
 }
-cxCoreInit.promise.then(function(){cxCoreInit();}).catch(function(e){postMessage({type:", m, ",value:e.toString()});})

+ 9 - 22
post_process.cjs

@@ -56,28 +56,15 @@ function processHtmlFiles(files) {
 }
 
 function appendToCxcore() {
-  console.log();
-  console.log("adding init call to cxcore");
-  console.log();
-  const cxcoreFile = path.resolve(__dirname, 'cxcore.js');
-  exec("tail -n 1 cxcore.js", (err, stdout) => {
-    if (err) {
-      console.error("Failed to exec tail command: ", err);
-      return;
-    }
-    const lastLine = stdout.trim();
-    const content = 'cxCoreInit.promise.then(function(){cxCoreInit();}).catch(function(e){postMessage({type:", m, ",value:e.toString()});})';
-
-    if (lastLine == content) {
-      console.log("All good! cxcore.js already contains correct last line, skipped");
-    } else {
-      try {
-        fs.appendFileSync(cxcoreFile, content);
-      } catch (err) {
-        console.error("Failed to append: ", err);
-      }
-    }
-  });
+  const cxcoreFile = path.resolve(directoryPath, 'cxcore.js');
+  const content = 'cxCoreInit.promise.then(function(){cxCoreInit();}).catch(function(e){postMessage({type:", m, ",value:e.toString()});})';
+
+  try {
+    fs.appendFileSync(cxcoreFile, content);
+    console.log(`\nappended cxCoreInit() to build/cxcore.js`);
+  } catch (err) {
+    console.error("Failed to append: ", err);
+  }
 }
 
 function extractScript(fileContent) {

+ 3 - 0
src/app.d.js

@@ -0,0 +1,3 @@
+import 'types/directSockets';
+
+export {};

+ 183 - 0
src/lib/socket-connection.ts

@@ -0,0 +1,183 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Socket connection web component
+ */
+class SocketConnection extends HTMLElement {
+  socket: TCPSocket | undefined;
+  reader: ReadableStreamDefaultReader | undefined;
+  address: string | undefined;
+  port: number | undefined;
+  incomingContent: HTMLElement | undefined;
+  logElement: HTMLElement | undefined;
+
+  constructor() {
+    super();
+  }
+
+  static get observedAttributes() {
+    return ['address', 'port', 'log'];
+  }
+
+  attributeChangedCallback(
+    property: string,
+    oldValue: string,
+    newValue: string,
+  ) {
+    if (oldValue === newValue) return;
+    if (property === 'address') {
+      this.address = newValue;
+    }
+
+    if (property === 'port') {
+      this.port = parseInt(newValue);
+    }
+
+    if (property === 'log' && this.logElement) {
+      console.log(`incoming content ${newValue}`);
+      this.logElement?.setAttribute('input', newValue);
+    }
+  }
+  /**
+   * Setup when socket connection component is appended to DOM.
+   */
+  async connectedCallback() {
+    const shadow = this.attachShadow({ mode: 'open' });
+
+    const template = document
+      .getElementById('socket-connection')
+      ?.content.cloneNode(true);
+
+    const logOutput = template.querySelector('#connection');
+
+    const messageInput = template.querySelector('#messageInput');
+    const sendButton = template.querySelector('#sendButton');
+    const disconnectButton = template.querySelector('#disconnectButton');
+
+    this.logElement = template.querySelector('#log');
+
+    // automatically connect to server
+    logOutput.textContent = 'Trying to connect...';
+    disconnectButton.disabled = true;
+    const connected = await this.connectToServer();
+    if (connected) {
+      disconnectButton.textContent = 'Disconnect';
+      disconnectButton.disabled = false;
+      logOutput.textContent = `Successfully connected to ${this.address} at port ${this.port}`;
+    } else {
+      disconnectButton.textContent = 'Remove socket';
+      disconnectButton.disabled = false;
+      logOutput.textContent = `Failed to connect to ${this.address} at port ${this.port}`;
+    }
+
+    sendButton.addEventListener('click', async (e) => {
+      e.preventDefault();
+      disconnectButton.disabled = true;
+
+      const sendEvent = new CustomEvent('send', {
+        bubbles: true,
+        cancelable: false,
+        detail: {
+          message: messageInput.value,
+        },
+      });
+      this.dispatchEvent(sendEvent);
+
+      disconnectButton.disabled = false;
+      messageInput.value = '';
+    });
+
+    disconnectButton.addEventListener('click', async () => {
+      sendButton.disabled = true;
+      messageInput.disabled = true;
+      await this.disconnectButtonCallback(disconnectButton, logOutput);
+    });
+
+    shadow.append(template);
+  }
+
+  /**
+   * Callback to handle disconnect button click events.
+   * Updates UI and calls methods for disconnecting from server.
+   */
+  async disconnectButtonCallback(
+    disconnectButton: HTMLButtonElement,
+    logOutput: HTMLParagraphElement,
+  ) {
+    if (this.socket) {
+      logOutput.textContent = 'Trying to disconnect...';
+      disconnectButton.disabled = true;
+      // disconnect from server
+      const disconnected = await this.disconnectFromServer();
+      if (disconnected) {
+        logOutput.textContent = `Disconnected from ${this.address} at port ${this.port}`;
+        const closeEvent = new CustomEvent('close', {
+          bubbles: true,
+          cancelable: false,
+        });
+        this.dispatchEvent(closeEvent);
+      } else {
+        disconnectButton.disabled = false;
+        logOutput.textContent = `Failed to disconnect from ${this.address} at port ${this.port}`;
+      }
+    }
+  }
+
+  /**
+   * Disconnect from server.
+   */
+  async disconnectFromServer(): Promise<boolean> {
+    try {
+      if (this.socket) {
+        if (this.reader) {
+          this.reader.releaseLock();
+        }
+        await this.socket.close();
+      }
+    } catch (e) {
+      console.log(e);
+      return Promise.resolve(false);
+    }
+    this.socket = undefined;
+    return Promise.resolve(true);
+  }
+
+  /**
+   * Connect to server.
+   */
+  async connectToServer(): Promise<boolean> {
+    try {
+      // Create socket
+      if (this.address && this.port) {
+        this.socket = new TCPSocket(this.address, this.port);
+      }
+
+      if (!this.socket) {
+        return Promise.resolve(false);
+      }
+    } catch (e) {
+      console.log(e);
+      await this.disconnectFromServer();
+      return Promise.resolve(false);
+    }
+
+    return Promise.resolve(true);
+  }
+}
+
+// customElements.define('socket-connection', SocketConnection);
+export default SocketConnection;

+ 69 - 0
src/lib/socket-log.ts

@@ -0,0 +1,69 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+class SocketLog extends HTMLElement {
+  input: string | undefined;
+  header: HTMLElement | undefined;
+  log: HTMLElement | undefined;
+
+  constructor() {
+    super();
+  }
+
+  static get observedAttributes() {
+    return ['input'];
+  }
+
+  attributeChangedCallback(
+    property: string,
+    oldValue: string,
+    newValue: string,
+  ) {
+    if (oldValue === newValue) {
+      return;
+    }
+
+    console.log(
+      `Attribute: ${property} changed from ${oldValue} to ${newValue}`,
+    );
+
+    if (property === 'input') {
+      this.input = newValue;
+      if (this.header) {
+        this.header.textContent = 'Latest transmission';
+      }
+      if (this.log) {
+        this.log.textContent = newValue;
+      }
+    }
+  }
+
+  async connectedCallback() {
+    const shadow = this.attachShadow({ mode: 'open' });
+    const template = document
+      .getElementById('socket-log')
+      ?.content.cloneNode(true);
+
+    // Add values to template
+    this.header = template.querySelector('#log-header');
+    this.log = template.querySelector('#log');
+
+    shadow.append(template);
+  }
+}
+
+// customElements.define('socket-log', SocketLog);
+export default SocketLog;

+ 141 - 0
src/lib/socket-server.ts

@@ -0,0 +1,141 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+class SocketServer extends HTMLElement {
+  server: TCPServerSocket | undefined;
+  connections: number | undefined;
+  bytes: number | undefined;
+  address: string | undefined;
+  port: number | undefined;
+  log: string | undefined;
+  portElement: HTMLElement | undefined;
+  addressElement: HTMLElement | undefined;
+  connectionsElement: HTMLElement | undefined;
+  bytesElement: HTMLElement | undefined;
+  logElement: HTMLElement | undefined;
+
+  constructor() {
+    super();
+  }
+
+  static get observedAttributes() {
+    return ['address', 'port', 'connections', 'bytes', 'log'];
+  }
+
+  update() {
+    if (this.portElement) {
+      this.portElement.textContent = this.port?.toString() || '';
+    }
+
+    if (this.addressElement) {
+      this.addressElement.textContent = this.address || '';
+    }
+
+    if (this.connectionsElement) {
+      this.connectionsElement.textContent = this.connections?.toString() || '0';
+    }
+
+    if (this.bytesElement) {
+      this.bytesElement.textContent = this.bytes?.toString() || '0';
+    }
+
+    if (this.logElement) {
+      if (this.log) {
+        this.logElement.setAttribute('input', this.log || '');
+      }
+    }
+  }
+
+  attributeChangedCallback(
+    property: string,
+    oldValue: string,
+    newValue: string,
+  ) {
+    if (oldValue === newValue) {
+      return;
+    }
+
+    console.log(
+      `Attribute ${property} changed from ${oldValue} to ${newValue}`,
+    );
+
+    switch (property) {
+      case 'address':
+        this.address = newValue;
+        break;
+      case 'port':
+        this.port = parseInt(newValue);
+        break;
+      case 'connections':
+        this.connections = parseInt(newValue);
+        break;
+      case 'bytes':
+        this.bytes = parseInt(newValue);
+        break;
+      case 'log':
+        this.log = newValue;
+        break;
+    }
+
+    this.update();
+  }
+
+  async connectedCallback() {
+    const shadow = this.attachShadow({ mode: 'open' });
+    const template = document
+      .getElementById('socket-server')
+      ?.content.cloneNode(true);
+
+    console.log('IN');
+
+    console.log(this.server);
+
+    // Add values to template
+    this.portElement = template.querySelector('#port') as HTMLElement;
+
+    this.addressElement = template.querySelector('#address') as HTMLElement;
+
+    this.connectionsElement = template.querySelector(
+      '#connections',
+    ) as HTMLElement;
+
+    this.bytesElement = template.querySelector('#bytes') as HTMLElement;
+
+    this.logElement = template.querySelector('#log') as HTMLElement;
+
+    const messageInput = template.querySelector('#messageInput');
+    const sendButton = template.querySelector('#sendButton');
+
+    sendButton.addEventListener('click', async (e) => {
+      e.preventDefault();
+      const sendEvent = new CustomEvent('send', {
+        bubbles: true,
+        cancelable: false,
+        detail: {
+          message: messageInput.value,
+        },
+      });
+      this.dispatchEvent(sendEvent);
+      messageInput.value = '';
+    });
+
+    shadow.append(template);
+    this.update();
+  }
+}
+
+// customElements.define('socket-server', SocketServer);
+export default SocketServer;

+ 85 - 0
src/lib/streams.ts

@@ -0,0 +1,85 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export async function readStream(
+  reader: ReadableStreamDefaultReader,
+  cb: (value: Uint8Array) => void,
+): Promise<void> {
+  // Read from the socket until it's closed
+  while (reader) {
+    // Wait for the next chunk
+    const { value, done } = await reader.read();
+
+    // Send the chunk to the callback
+    if (value) {
+      cb(value);
+    }
+
+    // Release the reader if we're done
+    if (done) {
+      reader.releaseLock();
+      break;
+    }
+  }
+}
+
+export async function writeStream(
+  socket: TCPSocket,
+  message: string,
+): Promise<void> {
+  // Wait for the socket to be opened
+  const connection = await socket.opened;
+  // Get a writer to write to the socket
+  const writer = connection?.writable.getWriter();
+  const encoder = new TextEncoder();
+  writer.write(encoder.encode(message));
+  writer.releaseLock();
+}
+export async function collectConnections(
+  server: TCPServerSocket,
+  infoCB: (address: string, port: number) => void,
+  connectionCB: (value: TCPSocket) => Promise<void>,
+): Promise<void> {
+  // Wait for the server to be opened
+  const { readable, localAddress, localPort } = await server.opened;
+
+  // Get a reader to read from the server
+  // These will be connections to the server
+  const connections = readable.getReader();
+
+  // Send the server info to the callback
+  infoCB(localAddress, localPort);
+
+  // Read connections from the server until all connections are closed
+  while (connections) {
+    const { value: connection, done } = await connections.read();
+
+    // Send the connection to the callback
+    if (connection) {
+      connectionCB(connection);
+    }
+
+    // Release the connection if we're done
+    if (done) {
+      connections.releaseLock();
+      break;
+    }
+  }
+
+  // Wait for the server to be closed
+  await server.closed;
+  console.log('Closed');
+}

+ 3 - 0
svelte.config.js

@@ -38,6 +38,9 @@ const config = {
 				'report-uri': ['.']
 			}
 		},
+		alias: {
+			'types': 'types/'
+		}
 	},
 	preprocess: vitePreprocess()
 };

+ 80 - 0
types/directSockets.d.ts

@@ -0,0 +1,80 @@
+/**
+ * Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* https://wicg.github.io/direct-sockets/#dom-tcpsocketoptions */
+interface TCPSocketOptions {
+  sendBufferSize?: number;
+  receiveBufferSize?: number;
+
+  noDelay?: boolean;
+  keepAliveDelay?: number;
+}
+
+/* https://wicg.github.io/direct-sockets/#dom-tcpsocketopeninfo */
+interface TCPSocketOpenInfo {
+  readable: ReadableStream<Uint8Array>;
+  writable: WritableStream<Uint8Array>;
+
+  remoteAddress: string;
+  remotePort: number;
+
+  localAddress: string;
+  localPort: number;
+}
+
+/**
+ * https://wicg.github.io/direct-sockets/#dom-tcpsocket
+ */
+declare class TCPSocket {
+  constructor(
+    remoteAddress: string,
+    remotePort: number,
+    options?: TCPSocketOptions,
+  );
+
+  opened: Promise<TCPSocketOpenInfo>;
+  closed: Promise<void>;
+
+  close(): Promise<void>;
+}
+
+/* https://wicg.github.io/direct-sockets/#dom-tcpserversocketoptions */
+interface TCPServerSocketOptions {
+  localPort?: number;
+  backlog?: number;
+
+  ipv6Only?: boolean;
+}
+
+/* https://wicg.github.io/direct-sockets/#dom-tcpserversocketopeninfo */
+interface TCPServerSocketOpenInfo {
+  readable: ReadableStream<TCPSocket>;
+
+  localAddress: string;
+  localPort: number;
+}
+
+/**
+ * https://wicg.github.io/direct-sockets/#dom-tcpserversocket
+ */
+declare class TCPServerSocket {
+  constructor(localAddress: string, options?: TCPServerSocketOptions);
+
+  opened: Promise<TCPServerSocketOpenInfo>;
+  closed: Promise<void>;
+
+  close(): Promise<void>;
+}