Skip to content

Commit 680047b

Browse files
authored
fix: JS API shouldn't require reference to window object (deephaven#6193)
Also introduces a dh.Event type to use instead of the browser Event and CustomEvent types. Fixes deephaven#5537 Fixes deephaven#6190
1 parent 8f02178 commit 680047b

39 files changed

+335
-430
lines changed

web/client-api/src/main/java/io/deephaven/web/DeephavenJsApiLinkerTemplate.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ function bindTo(target, source) {
1515

1616
var Scope = function () {
1717
};
18-
Scope.prototype = self;
18+
Scope.prototype = globalThis;
1919
var $doc, $entry, $moduleName, $moduleBase;
2020
var $wnd = new Scope();
21-
bindTo($wnd, self);
22-
var dh = {}
21+
bindTo($wnd, globalThis);
22+
var window = $wnd;
23+
var dh = {};
2324
$wnd.dh = dh;
2425
import {dhinternal} from './dh-internal.js';
2526
$wnd.dhinternal = dhinternal;

web/client-api/src/main/java/io/deephaven/web/client/api/Callbacks.java

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import elemental2.dom.DomGlobal;
77
import elemental2.promise.Promise;
88
import elemental2.promise.Promise.PromiseExecutorCallbackFn.RejectCallbackFn;
9+
import io.deephaven.web.client.api.event.HasEventHandling;
910
import io.deephaven.web.shared.fu.JsBiConsumer;
1011

1112
import javax.annotation.Nullable;

web/client-api/src/main/java/io/deephaven/web/client/api/ConnectOptions.java

+43-3
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,47 @@
33
//
44
package io.deephaven.web.client.api;
55

6+
import elemental2.core.Function;
67
import jsinterop.annotations.JsIgnore;
8+
import jsinterop.annotations.JsNullable;
79
import jsinterop.annotations.JsType;
810
import jsinterop.base.Js;
911
import jsinterop.base.JsPropertyMap;
1012

1113
/**
12-
* Presently optional and not used by the server, this allows the client to specify some authentication details. String
13-
* authToken <i>- base 64 encoded auth token. String serviceId -</i> The service ID to use for the connection.
14+
* Addition optional configuration that can be passed to the {@link CoreClient} constructor.
1415
*/
1516
@JsType(namespace = "dh")
1617
public class ConnectOptions {
18+
/**
19+
* Optional map of http header names and values to send to the server with each request.
20+
*/
21+
@JsNullable
1722
public JsPropertyMap<String> headers = Js.uncheckedCast(JsPropertyMap.of());
1823

24+
/**
25+
* True to enable debug logging. At this time, only enables logging for gRPC calls.
26+
*/
27+
@JsNullable
28+
public boolean debug = false;
29+
30+
/**
31+
* Set this to true to force the use of websockets when connecting to the deephaven instance, false to force the use
32+
* of {@code fetch}.
33+
* <p>
34+
* Defaults to null, indicating that the server URL should be checked to see if we connect with fetch or websockets.
35+
*/
36+
@JsNullable
37+
public Boolean useWebsockets;
38+
39+
// TODO (deephaven-core#6214) provide our own grpc-web library that can replace fetch
40+
// /**
41+
// * Optional fetch implementation to use instead of the global {@code fetch()} call, allowing callers to provide a
42+
// * polyfill rather than add a new global.
43+
// */
44+
// @JsNullable
45+
// public Function fetch;
46+
1947
public ConnectOptions() {
2048

2149
}
@@ -24,6 +52,18 @@ public ConnectOptions() {
2452
public ConnectOptions(Object connectOptions) {
2553
this();
2654
JsPropertyMap<Object> map = Js.asPropertyMap(connectOptions);
27-
headers = Js.uncheckedCast(map.getAsAny("headers").asPropertyMap());
55+
if (map.has("headers")) {
56+
headers = Js.uncheckedCast(map.getAsAny("headers").asPropertyMap());
57+
}
58+
if (map.has("debug")) {
59+
debug = map.getAsAny("debug").asBoolean();
60+
}
61+
if (map.has("useWebsockets")) {
62+
useWebsockets = map.getAsAny("useWebsockets").asBoolean();
63+
}
64+
// TODO (deephaven-core#6214) provide our own grpc-web library that can replace fetch
65+
// if (map.has("fetch")) {
66+
// fetch = map.getAsAny("fetch").uncheckedCast();
67+
// }
2868
}
2969
}

web/client-api/src/main/java/io/deephaven/web/client/api/CoreClient.java

+16-19
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import io.deephaven.javascript.proto.dhinternal.io.deephaven.proto.config_pb.ConfigurationConstantsResponse;
1515
import io.deephaven.javascript.proto.dhinternal.io.deephaven.proto.config_pb_service.ConfigServiceClient;
1616
import io.deephaven.javascript.proto.dhinternal.jspb.Map;
17+
import io.deephaven.web.client.api.event.HasEventHandling;
1718
import io.deephaven.web.client.api.storage.JsStorageService;
1819
import io.deephaven.web.client.fu.JsLog;
1920
import io.deephaven.web.client.fu.LazyPromise;
@@ -29,8 +30,6 @@
2930
import java.util.Objects;
3031
import java.util.function.Consumer;
3132

32-
import static io.deephaven.web.client.api.barrage.WebGrpcUtils.CLIENT_OPTIONS;
33-
3433
@JsType(namespace = "dh")
3534
public class CoreClient extends HasEventHandling {
3635
public static final String EVENT_CONNECT = "connect",
@@ -48,13 +47,11 @@ public class CoreClient extends HasEventHandling {
4847
private final IdeConnection ideConnection;
4948

5049
public CoreClient(String serverUrl, @TsTypeRef(ConnectOptions.class) @JsOptional Object connectOptions) {
51-
ideConnection = new IdeConnection(serverUrl, connectOptions, true);
50+
ideConnection = new IdeConnection(serverUrl, connectOptions);
5251

5352
// For now the only real connection is the IdeConnection, so we re-fire the auth token refresh
5453
// event here for the UI to listen to
55-
ideConnection.addEventListener(EVENT_REFRESH_TOKEN_UPDATED, event -> {
56-
fireEvent(EVENT_REFRESH_TOKEN_UPDATED, event);
57-
});
54+
ideConnection.addEventListener(EVENT_REFRESH_TOKEN_UPDATED, this::fireEvent);
5855
}
5956

6057
private <R> Promise<String[][]> getConfigs(Consumer<JsBiConsumer<Object, R>> rpcCall,
@@ -82,20 +79,20 @@ public String getServerUrl() {
8279
}
8380

8481
public Promise<String[][]> getAuthConfigValues() {
85-
return ideConnection.getConnectOptions().then(options -> {
86-
BrowserHeaders metadata = new BrowserHeaders();
87-
JsObject.keys(options.headers).forEach((key, index) -> {
88-
metadata.set(key, options.headers.get(key));
89-
return null;
90-
});
91-
return getConfigs(
92-
// Explicitly creating a new client, and not passing auth details, so this works pre-connection
93-
c -> new ConfigServiceClient(getServerUrl(), CLIENT_OPTIONS).getAuthenticationConstants(
94-
new AuthenticationConstantsRequest(),
95-
metadata,
96-
c::apply),
97-
AuthenticationConstantsResponse::getConfigValuesMap);
82+
BrowserHeaders metadata = new BrowserHeaders();
83+
JsPropertyMap<String> headers = ideConnection.getOptions().headers;
84+
JsObject.keys(headers).forEach((key, index) -> {
85+
metadata.set(key, headers.get(key));
86+
return null;
9887
});
88+
ConfigServiceClient configService = ideConnection.createClient(ConfigServiceClient::new);
89+
return getConfigs(
90+
// Explicitly creating a new client, and not passing auth details, so this works pre-connection
91+
c -> configService.getAuthenticationConstants(
92+
new AuthenticationConstantsRequest(),
93+
metadata,
94+
c::apply),
95+
AuthenticationConstantsResponse::getConfigValuesMap);
9996
}
10097

10198
public Promise<Void> login(@TsTypeRef(LoginCredentials.class) JsPropertyMap<Object> credentials) {

web/client-api/src/main/java/io/deephaven/web/client/api/EventFn.java

-15
This file was deleted.

web/client-api/src/main/java/io/deephaven/web/client/api/JsPartitionedTable.java

+5-13
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66
import elemental2.core.JsArray;
77
import elemental2.core.JsObject;
88
import elemental2.core.JsSet;
9-
import elemental2.dom.CustomEvent;
10-
import elemental2.dom.CustomEventInit;
11-
import elemental2.dom.Event;
129
import elemental2.promise.Promise;
1310
import io.deephaven.javascript.proto.dhinternal.io.deephaven.proto.partitionedtable_pb.GetTableRequest;
1411
import io.deephaven.javascript.proto.dhinternal.io.deephaven.proto.partitionedtable_pb.MergeRequest;
@@ -18,6 +15,7 @@
1815
import io.deephaven.web.client.api.barrage.WebBarrageUtils;
1916
import io.deephaven.web.client.api.barrage.def.ColumnDefinition;
2017
import io.deephaven.web.client.api.barrage.def.InitialTableDefinition;
18+
import io.deephaven.web.client.api.event.Event;
2119
import io.deephaven.web.client.api.lifecycle.HasLifecycle;
2220
import io.deephaven.web.client.api.subscription.SubscriptionTableData;
2321
import io.deephaven.web.client.api.subscription.TableSubscription;
@@ -112,10 +110,8 @@ public Promise<JsPartitionedTable> refetch() {
112110
fireEvent(EVENT_RECONNECT);
113111
return null;
114112
}, failure -> {
115-
CustomEventInit<Object> init = CustomEventInit.create();
116-
init.setDetail(failure);
117113
unsuppressEvents();
118-
fireEvent(EVENT_RECONNECTFAILED, init);
114+
fireEvent(EVENT_RECONNECTFAILED, failure);
119115
suppressEvents();
120116
return null;
121117
});
@@ -141,20 +137,16 @@ private Promise<JsPartitionedTable> subscribeToBaseTable() {
141137
return promise.asPromise();
142138
}
143139

144-
private void handleKeys(Event update) {
145-
// noinspection unchecked
146-
CustomEvent<SubscriptionTableData> event = (CustomEvent<SubscriptionTableData>) update;
140+
private void handleKeys(Event<SubscriptionTableData> update) {
147141

148142
// We're only interested in added rows, send an event indicating the new keys that are available
149-
SubscriptionTableData eventData = event.detail;
143+
SubscriptionTableData eventData = update.getDetail();
150144
RangeSet added = eventData.getAdded().getRange();
151145
added.indexIterator().forEachRemaining((long index) -> {
152146
// extract the key to use
153147
JsArray<Object> key = eventData.getColumns().map((c, p1) -> eventData.getData(index, c));
154148
knownKeys.add(key.asList());
155-
CustomEventInit<JsArray<Object>> init = CustomEventInit.create();
156-
init.setDetail(key);
157-
fireEvent(EVENT_KEYADDED, init);
149+
fireEvent(EVENT_KEYADDED, key);
158150
});
159151
}
160152

web/client-api/src/main/java/io/deephaven/web/client/api/JsTable.java

+2-7
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import com.vertispan.tsdefs.annotations.TsUnion;
99
import com.vertispan.tsdefs.annotations.TsUnionMember;
1010
import elemental2.core.JsArray;
11-
import elemental2.dom.CustomEventInit;
1211
import elemental2.promise.IThenable.ThenOnFulfilledCallbackFn;
1312
import elemental2.promise.Promise;
1413
import io.deephaven.javascript.proto.dhinternal.io.deephaven.proto.hierarchicaltable_pb.RollupRequest;
@@ -1677,9 +1676,7 @@ public void setState(final ClientTableState state) {
16771676
});
16781677
});
16791678
}
1680-
final CustomEventInit init = CustomEventInit.create();
1681-
init.setDetail(state);
1682-
fireEvent(INTERNAL_EVENT_STATECHANGED, init);
1679+
fireEvent(INTERNAL_EVENT_STATECHANGED, state);
16831680
}
16841681
}
16851682

@@ -1748,9 +1745,7 @@ public void setSize(double s) {
17481745
// If the size changed, and we have no subscription active, fire. Otherwise, we want to let the
17491746
// subscription itself manage this, so that the size changes are synchronized with data changes,
17501747
// and consumers won't be confused by the table size not matching data.
1751-
CustomEventInit event = CustomEventInit.create();
1752-
event.setDetail(s);
1753-
fireEvent(JsTable.EVENT_SIZECHANGED, event);
1748+
fireEvent(JsTable.EVENT_SIZECHANGED, s);
17541749
}
17551750
fireEvent(JsTable.INTERNAL_EVENT_SIZELISTENER);
17561751
}

web/client-api/src/main/java/io/deephaven/web/client/api/JsTotalsTable.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
import com.vertispan.tsdefs.annotations.TsTypeRef;
99
import elemental2.core.JsArray;
1010
import elemental2.core.JsString;
11-
import elemental2.dom.CustomEvent;
1211
import elemental2.promise.Promise;
1312
import io.deephaven.javascript.proto.dhinternal.io.deephaven.proto.ticket_pb.TypedTicket;
1413
import io.deephaven.web.client.api.console.JsVariableType;
14+
import io.deephaven.web.client.api.event.Event;
15+
import io.deephaven.web.client.api.event.EventFn;
1516
import io.deephaven.web.client.api.filter.FilterCondition;
1617
import io.deephaven.web.client.api.subscription.AbstractTableSubscription;
1718
import io.deephaven.web.client.api.subscription.ViewportData;
@@ -223,7 +224,7 @@ public <T> boolean removeEventListener(String name, EventFn<T> callback) {
223224
}
224225

225226
@JsMethod
226-
public <T> Promise<CustomEvent<T>> nextEvent(String eventName, Double timeoutInMillis) {
227+
public <T> Promise<Event<T>> nextEvent(String eventName, Double timeoutInMillis) {
227228
return wrappedTable.nextEvent(eventName, timeoutInMillis);
228229
}
229230

web/client-api/src/main/java/io/deephaven/web/client/api/QueryConnectable.java

+32-7
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
import com.vertispan.tsdefs.annotations.TsIgnore;
77
import elemental2.core.JsArray;
88
import elemental2.core.JsSet;
9-
import elemental2.dom.CustomEventInit;
10-
import elemental2.dom.DomGlobal;
119
import elemental2.promise.Promise;
10+
import io.deephaven.javascript.proto.dhinternal.grpcweb.Grpc;
11+
import io.deephaven.javascript.proto.dhinternal.grpcweb.client.RpcOptions;
1212
import io.deephaven.javascript.proto.dhinternal.io.deephaven.proto.session_pb.TerminationNotificationResponse;
13+
import io.deephaven.web.client.api.event.HasEventHandling;
14+
import io.deephaven.web.client.api.grpc.MultiplexedWebsocketTransport;
1315
import io.deephaven.web.client.ide.IdeSession;
1416
import io.deephaven.javascript.proto.dhinternal.io.deephaven.proto.console_pb.*;
1517
import io.deephaven.javascript.proto.dhinternal.io.deephaven.proto.ticket_pb.Ticket;
@@ -26,6 +28,7 @@
2628

2729
import java.util.ArrayList;
2830
import java.util.List;
31+
import java.util.function.BiFunction;
2932

3033
import static io.deephaven.web.client.ide.IdeConnection.HACK_CONNECTION_FAILURE;
3134
import static io.deephaven.web.shared.fu.PromiseLike.CANCELLATION_MESSAGE;
@@ -51,9 +54,9 @@ public QueryConnectable() {
5154
this.connection = JsLazy.of(() -> new WorkerConnection(this));
5255
}
5356

54-
public abstract Promise<ConnectToken> getConnectToken();
57+
public abstract ConnectToken getToken();
5558

56-
public abstract Promise<ConnectOptions> getConnectOptions();
59+
public abstract ConnectOptions getOptions();
5760

5861
@Deprecated
5962
public void notifyConnectionError(ResponseStreamWrapper.Status status) {
@@ -62,12 +65,10 @@ public void notifyConnectionError(ResponseStreamWrapper.Status status) {
6265
}
6366
notifiedConnectionError = true;
6467

65-
CustomEventInit<JsPropertyMap<Object>> event = CustomEventInit.create();
66-
event.setDetail(JsPropertyMap.of(
68+
fireEvent(HACK_CONNECTION_FAILURE, JsPropertyMap.of(
6769
"status", status.getCode(),
6870
"details", status.getDetails(),
6971
"metadata", status.getMetadata()));
70-
fireEvent(HACK_CONNECTION_FAILURE, event);
7172
JsLog.warn(
7273
"The event dh.IdeConnection.HACK_CONNECTION_FAILURE is deprecated and will be removed in a later release");
7374
}
@@ -244,4 +245,28 @@ public void disconnected() {
244245
}
245246

246247
public abstract void notifyServerShutdown(TerminationNotificationResponse success);
248+
249+
public boolean useWebsockets() {
250+
Boolean useWebsockets = getOptions().useWebsockets;
251+
if (useWebsockets == null) {
252+
useWebsockets = getServerUrl().startsWith("http:");
253+
}
254+
return useWebsockets;
255+
}
256+
257+
public <T> T createClient(BiFunction<String, Object, T> constructor) {
258+
return constructor.apply(getServerUrl(), makeRpcOptions());
259+
}
260+
261+
public RpcOptions makeRpcOptions() {
262+
RpcOptions options = RpcOptions.create();
263+
options.setDebug(getOptions().debug);
264+
if (useWebsockets()) {
265+
// Replace with our custom websocket impl, with fallback to the built-in one
266+
options.setTransport(o -> new MultiplexedWebsocketTransport(o, () -> {
267+
Grpc.setDefaultTransport.onInvoke(Grpc.WebsocketTransport.onInvoke());
268+
}));
269+
}
270+
return options;
271+
}
247272
}

0 commit comments

Comments
 (0)