|
@@ -0,0 +1,231 @@
|
|
|
+
|
|
|
+ * EliasDB - JavaScript GraphQL client library (http version)
|
|
|
+ *
|
|
|
+ * Copyright 2019 Matthias Ladkau. All rights reserved.
|
|
|
+ *
|
|
|
+ * This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
+ * file, You can obtain one at http:
|
|
|
+ *
|
|
|
+ */
|
|
|
+export enum RequestMetod {
|
|
|
+ Post = 'post',
|
|
|
+ Get = 'get',
|
|
|
+}
|
|
|
+
|
|
|
+export class EliasDBGraphQLClient {
|
|
|
+
|
|
|
+ * Host this client is connected to.
|
|
|
+ */
|
|
|
+ protected host: string;
|
|
|
+
|
|
|
+
|
|
|
+ * Partition this client is working on.
|
|
|
+ */
|
|
|
+ protected partition: string;
|
|
|
+
|
|
|
+
|
|
|
+ * Websocket over which we can handle subscriptions.
|
|
|
+ */
|
|
|
+ private ws?: WebSocket;
|
|
|
+
|
|
|
+
|
|
|
+ * EliasDB GraphQL endpoints.
|
|
|
+ */
|
|
|
+ private graphQLEndpoint: string;
|
|
|
+ private graphQLReadOnlyEndpoint: string;
|
|
|
+
|
|
|
+
|
|
|
+ * List of operations to execute once the websocket connection is established.
|
|
|
+ */
|
|
|
+ private delayedOperations: {(): void}[] = [];
|
|
|
+
|
|
|
+
|
|
|
+ * Queue of subscriptions which await an id;
|
|
|
+ */
|
|
|
+ private subscriptionQueue: {(data: any): void}[] = [];
|
|
|
+
|
|
|
+
|
|
|
+ * Map of active subscriptions.
|
|
|
+ */
|
|
|
+ private subscriptionCallbacks: {[id: string]: {(data: any): void}} = {};
|
|
|
+
|
|
|
+
|
|
|
+ * Createa a new EliasDB GraphQL Client.
|
|
|
+ *
|
|
|
+ * @param host Host to connect to.
|
|
|
+ * @param partition Partition to query.
|
|
|
+ */
|
|
|
+ public constructor(
|
|
|
+ host: string = window.location.host,
|
|
|
+ partition: string = 'main',
|
|
|
+ ) {
|
|
|
+ this.host = host;
|
|
|
+ this.partition = partition;
|
|
|
+ this.graphQLEndpoint = `http://${host}/db/v1/graphql/${partition}`;
|
|
|
+ this.graphQLReadOnlyEndpoint = `http://${host}/db/v1/graphql-query/${partition}`;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * Initialize a websocket to support subscriptions.
|
|
|
+ */
|
|
|
+ private initWebsocket() {
|
|
|
+ const url = `ws://${this.host}/db/v1/graphql-subscriptions/${this.partition}`;
|
|
|
+ this.ws = new WebSocket(url);
|
|
|
+ this.ws.onmessage = this.message.bind(this);
|
|
|
+
|
|
|
+ this.ws.onopen = () => {
|
|
|
+ if (this.ws) {
|
|
|
+ this.ws.send(
|
|
|
+ JSON.stringify({
|
|
|
+ type: 'init',
|
|
|
+ payload: {},
|
|
|
+ }),
|
|
|
+ );
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * Run a GraphQL query or mutation and return the response.
|
|
|
+ *
|
|
|
+ * @param query Query to run.
|
|
|
+ * @param variables List of variable values. The query must define these
|
|
|
+ * variables.
|
|
|
+ * @param operationName Name of the named operation to run. The query must
|
|
|
+ * specify this named operation.
|
|
|
+ * @param method Request method to use. Get requests cannot run mutations.
|
|
|
+ */
|
|
|
+ public req(
|
|
|
+ query: string,
|
|
|
+ variables: {[key: string]: any} = {},
|
|
|
+ operationName: string = '',
|
|
|
+ method: RequestMetod = RequestMetod.Post,
|
|
|
+ ): Promise<any> {
|
|
|
+ const http = new XMLHttpRequest();
|
|
|
+
|
|
|
+ const toSend: {[key: string]: any} = {
|
|
|
+ operationName,
|
|
|
+ variables,
|
|
|
+ query,
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ if (method === RequestMetod.Post) {
|
|
|
+ http.open(method, this.graphQLEndpoint, true);
|
|
|
+ } else {
|
|
|
+ const params = Object.keys(toSend)
|
|
|
+ .map(key => {
|
|
|
+ const val =
|
|
|
+ key !== 'variables'
|
|
|
+ ? toSend[key]
|
|
|
+ : JSON.stringify(toSend[key]);
|
|
|
+ return `${key}=${encodeURIComponent(val)}`;
|
|
|
+ })
|
|
|
+ .join('&');
|
|
|
+ const url = `${this.graphQLReadOnlyEndpoint}?${params}`;
|
|
|
+
|
|
|
+ http.open(method, url, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ http.setRequestHeader('content-type', 'application/json');
|
|
|
+
|
|
|
+ return new Promise(function(resolve, reject) {
|
|
|
+ http.onload = function() {
|
|
|
+ try {
|
|
|
+ if (http.status === 200) {
|
|
|
+ resolve(http.response);
|
|
|
+ } else {
|
|
|
+ let err: string;
|
|
|
+ try {
|
|
|
+ err = JSON.parse(http.responseText)['errors'];
|
|
|
+ } catch {
|
|
|
+ err = http.responseText.trim();
|
|
|
+ }
|
|
|
+ reject(err);
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ reject(e);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ if (method === RequestMetod.Post) {
|
|
|
+ http.send(JSON.stringify(toSend));
|
|
|
+ } else {
|
|
|
+ http.send();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * Run a GraphQL subscription and receive updates if the data changes.
|
|
|
+ *
|
|
|
+ * @param query Query to run.
|
|
|
+ * @param update Update callback.
|
|
|
+ */
|
|
|
+ public subscribe(
|
|
|
+ query: string,
|
|
|
+ update: (data: any) => void,
|
|
|
+ variables: any = null,
|
|
|
+ ) {
|
|
|
+ if (!this.ws) {
|
|
|
+ this.initWebsocket();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.ws) {
|
|
|
+ const that = this;
|
|
|
+ const subscribeCall = function() {
|
|
|
+ if (that.ws) {
|
|
|
+ that.ws.send(
|
|
|
+ JSON.stringify({
|
|
|
+ id: that.subscriptionQueue.length,
|
|
|
+ query,
|
|
|
+ type: 'subscription_start',
|
|
|
+ variables: null,
|
|
|
+ }),
|
|
|
+ );
|
|
|
+ that.subscriptionQueue.push(update);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ if (this.ws.readyState !== WebSocket.OPEN) {
|
|
|
+ this.delayedOperations.push(subscribeCall);
|
|
|
+ } else {
|
|
|
+ subscribeCall();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * Process a new websocket message.
|
|
|
+ *
|
|
|
+ * @param msg New message.
|
|
|
+ */
|
|
|
+ protected message(msg: MessageEvent) {
|
|
|
+ const pmsg = JSON.parse(msg.data);
|
|
|
+
|
|
|
+ if (pmsg.type == 'init_success') {
|
|
|
+
|
|
|
+
|
|
|
+ this.delayedOperations.forEach(c => c());
|
|
|
+ this.delayedOperations = [];
|
|
|
+ } else if (pmsg.type == 'subscription_success') {
|
|
|
+ const callback = this.subscriptionQueue.shift();
|
|
|
+ if (callback) {
|
|
|
+ const id = pmsg.id;
|
|
|
+ this.subscriptionCallbacks[id] = callback;
|
|
|
+ }
|
|
|
+ } else if (pmsg.type == 'subscription_data') {
|
|
|
+ const callback = this.subscriptionCallbacks[pmsg.id];
|
|
|
+ if (callback) {
|
|
|
+ callback(pmsg.payload);
|
|
|
+ }
|
|
|
+ } else if (pmsg.type == 'subscription_fail') {
|
|
|
+ console.error(
|
|
|
+ 'Subscription failed: ',
|
|
|
+ pmsg.payload.errors.join('; '),
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|