123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- /**
- * 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://mozilla.org/MPL/2.0/.
- *
- */
- 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,
- };
- // Send an async ajax call
- 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') {
- // Execute the delayed operations
- 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('; '),
- );
- }
- }
- }
|