Skip to content

Commit b2b1364

Browse files
committed
perspective-js: now with more Proxy Session
Add support in perspective-js for creating proxy sessions from a connected client. Signed-off-by: Tom Jakubowski <tom@prospective.dev>
1 parent b2b6a67 commit b2b1364

File tree

6 files changed

+190
-3
lines changed

6 files changed

+190
-3
lines changed

.devcontainer/devcontainer.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
2+
// README at: https://github.com/devcontainers/templates/tree/main/src/universal
3+
{
4+
"name": "Ubuntu: pnpm, rust, cmake, for JS dev",
5+
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
6+
"customizations": {
7+
"vscode": {
8+
"extensions": [
9+
"rust-lang.rust-analyzer",
10+
"ms-playwright.playwright"
11+
]
12+
}
13+
},
14+
15+
"features": {
16+
"ghcr.io/devcontainers/features/rust:1": {},
17+
"ghcr.io/devcontainers-extra/features/pnpm:2": {}
18+
},
19+
20+
"postCreateCommand": "./.devcontainer/postcreate.sh"
21+
}

.devcontainer/postcreate.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
sudo apt-get update
6+
7+
# Install cmake
8+
test -f /usr/share/doc/kitware-archive-keyring/copyright ||
9+
wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | sudo tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null
10+
11+
echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ jammy main' | sudo tee /etc/apt/sources.list.d/kitware.list >/dev/null
12+
sudo apt-get update
13+
sudo apt-get install -y kitware-archive-keyring
14+
sudo apt-get install -y cmake
15+
16+
# Repo setup
17+
pnpm install

.vscode/extensions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"recommendations": ["rust-lang.rust", "ms-vscode.cpptools-extension-pack"]
2+
"recommendations": ["rust-lang.rust-analyzer", "ms-vscode.cpptools-extension-pack"]
33
}

rust/perspective-client/src/rust/session.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ pub struct ProxySession {
9090
}
9191

9292
impl ProxySession {
93-
pub async fn new(
93+
pub fn new(
9494
client: Client,
9595
send_response: impl Fn(&[u8]) -> Result<(), Box<dyn std::error::Error + Send + Sync>>
9696
+ Send

rust/perspective-js/src/rust/client.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use js_sys::{Function, Uint8Array};
1414
use macro_rules_attribute::apply;
1515
#[cfg(doc)]
1616
use perspective_client::SystemInfo;
17-
use perspective_client::{TableData, TableInitOptions};
17+
use perspective_client::{Session, TableData, TableInitOptions};
1818
use wasm_bindgen::prelude::*;
1919

2020
pub use crate::table::*;
@@ -32,6 +32,51 @@ extern "C" {
3232
pub type JsTableInitOptions;
3333
}
3434

35+
#[wasm_bindgen]
36+
#[derive(Clone)]
37+
pub struct ProxySession(perspective_client::ProxySession);
38+
39+
#[wasm_bindgen]
40+
impl ProxySession {
41+
#[wasm_bindgen(constructor)]
42+
pub fn new(client: &Client, on_response: &Function) -> Self {
43+
let poll_loop = LocalPollLoop::new({
44+
let on_response = on_response.clone();
45+
move |msg: Vec<u8>| {
46+
let msg = Uint8Array::from(&msg[..]);
47+
on_response.call1(&JsValue::UNDEFINED, &JsValue::from(msg))?;
48+
Ok(JsValue::null())
49+
}
50+
});
51+
// NB: This swallows any errors raised by the inner callback
52+
let on_response = Box::new(move |msg: &[u8]| {
53+
wasm_bindgen_futures::spawn_local(poll_loop.poll(msg.to_vec()));
54+
Ok(())
55+
});
56+
Self(perspective_client::ProxySession::new(
57+
client.client.clone(),
58+
on_response,
59+
))
60+
}
61+
62+
#[wasm_bindgen]
63+
pub async fn handle_request(&self, data: &[u8]) -> ApiResult<()> {
64+
use perspective_client::Session;
65+
self.0.handle_request(data).await?;
66+
Ok(())
67+
}
68+
69+
#[wasm_bindgen]
70+
pub async fn poll(&self) -> ApiResult<()> {
71+
self.0.poll().await?;
72+
Ok(())
73+
}
74+
75+
pub async fn close(self) {
76+
self.0.close().await;
77+
}
78+
}
79+
3580
#[apply(inherit_docs)]
3681
#[inherit_doc = "client.md"]
3782
#[wasm_bindgen]
@@ -61,6 +106,11 @@ impl Client {
61106
Client { close, client }
62107
}
63108

109+
#[wasm_bindgen]
110+
pub fn new_proxy_session(&self, on_response: &Function) -> ProxySession {
111+
ProxySession::new(self, on_response)
112+
}
113+
64114
#[wasm_bindgen]
65115
pub async fn init(&self) -> ApiResult<()> {
66116
self.client.clone().init().await?;
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
8+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
10+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12+
13+
import { test, expect } from "@finos/perspective-test";
14+
import { make_client, make_server } from "@finos/perspective";
15+
16+
test("Proxy session tunnels requests through client", async () => {
17+
// test.setTimeout(2000);
18+
const { client, server } = connectClientToServer();
19+
const { proxyClient } = connectProxyClient(client);
20+
21+
// Verify that main client + proxy client observe the same server
22+
const clientTables1 = await client.get_hosted_table_names();
23+
const proxyTables1 = await proxyClient.get_hosted_table_names();
24+
expect(proxyTables1).toStrictEqual([]);
25+
expect(proxyTables1).toStrictEqual(clientTables1);
26+
const name = "abc-" + Math.random();
27+
const _table = await client.table({ abc: [123] }, { name });
28+
const clientTables2 = await client.get_hosted_table_names();
29+
const proxyTables2 = await proxyClient.get_hosted_table_names();
30+
expect(proxyTables2).toStrictEqual([name]);
31+
expect(proxyTables2).toStrictEqual(clientTables2);
32+
});
33+
34+
test("Proxy session tunnels on_update callbacks through client", async () => {
35+
// test.setTimeout(2000);
36+
const { client } = connectClientToServer();
37+
const { proxyClient } = connectProxyClient(client);
38+
const name = "abc-" + Math.random();
39+
const clientTable = await client.table({ abc: [123] }, { name });
40+
41+
// Add an on_update callback to the proxy client's view of the table
42+
const proxyTable = await proxyClient.open_table(name);
43+
const proxyView = await proxyTable.view();
44+
let resolveUpdate;
45+
const onUpdateResp = new Promise((r) => (resolveUpdate = r));
46+
await proxyView.on_update(
47+
(x) => {
48+
resolveUpdate(x);
49+
},
50+
{ mode: "row" }
51+
);
52+
53+
// Enact table update through client's table handle, and assert that proxy
54+
// client's on_update callback is called
55+
await clientTable.update({ abc: [999] });
56+
const expectUpdate = expect.poll(
57+
async () => {
58+
const data = await onUpdateResp;
59+
// TODO: construct table out of onUpdateResp, assert contents?
60+
console.log("onUpdateResp, with data", data);
61+
return data;
62+
},
63+
{
64+
message: "Ensure proxy view updates with table",
65+
timeout: 10000,
66+
}
67+
);
68+
69+
expect(await proxyView.to_columns()).toStrictEqual({ abc: [123, 999] });
70+
71+
await expectUpdate.toHaveProperty("delta");
72+
await expectUpdate.toHaveProperty("port_id");
73+
});
74+
75+
function connectClientToServer() {
76+
const server = make_server();
77+
const session = server.make_session((msg) => {
78+
client.handle_response(msg);
79+
});
80+
const client = make_client((msg) => {
81+
session.handle_request(msg);
82+
});
83+
return {
84+
client,
85+
server,
86+
};
87+
}
88+
89+
function connectProxyClient(client) {
90+
const sess = client.new_proxy_session((res) => {
91+
proxyClient.handle_response(res);
92+
});
93+
const proxyClient = make_client((msg) => {
94+
sess.handle_request(msg);
95+
});
96+
return {
97+
proxyClient,
98+
};
99+
}

0 commit comments

Comments
 (0)