Skip to main content

Module http

About 3 minModulehttpcore

GitHub package.json version (subfolder of monorepo)
Namespacedescription
@equinor/fusion-framework-module-httpmodule interface
@equinor/fusion-framework-module-http/clienthttp client
@equinor/fusion-framework-module-http/selectorsrequest selectors
@equinor/fusion-framework-module-http/operatorsrequest and response operators

Configure

This module allows configuration of http client which would be used in runtime.

Each configuration of clients are mapped to key and initiated when runtime requests a client.

pre configuring

Clients can be provided on the fly, but as good practice, all clients should be defined in config.ts

Simple
import { configureHttpClient } from '@equinor/fusion-framework-module-http';

config.addConfig(configureHttpClient(
  'my-client',
  /** HttpClientOptions */
  {
    baseUri: 'https://foo.bar/api';
    defaultScopes: ['my-scope/.default']
  }
));

HttpClientOptions

baseUri? string

base endpoint for client

defaultScopes? Array<string>

array of OAuth scopeopen in new window for acquiring token

ctor? HttpClientConstructor<IHttpClient> caution

Bring your own implementation of IHttpClient

onCreate? (client: TClient) => void; advance

Manipulate IHttpClient instance before usage in runtime

requestHandler? HttpRequestHandler<HttpClientRequestInitType<IHttpClient>> caution

Provide a custom HttpRequestHandler.

instead of replacing the handler, add handler onCreate or configure client with a callback

responseHandler? HttpResponseHandler<HttpClientRequestInitType<IHttpClient>> caution

Provide a custom HttpResponseHandler.

instead of replacing the handler, add handler onCreate or configure client with a callback

Usage

Creating a client

const client = modules.http.createClient('foo');

Fetch

Async
client.fetch('/api', {selector}).then(console.log);

The fetch request arguments are the same as Web Fetch APIopen in new window, but has additional scope and selector property.

Scopes optional

An application can request one or more scopes, this information is then presented to the user in the consent screen, and the access token issued to the request will be limited to the scopes granted.

Scopes are normally configured for the client, so that by conveniences all request done to that base URI will have that scope. But sometimes a endpoint on that URI requires other scopes.

Selector optional

Selectors are callback for processing the response.

When a selector is provided, the return type of client.fetch will be the same as return type of the selector.

Tips

Selectors should be used, since they map Domain Transfer Object (DTO) to Value Objects (VO)

Secondly by abstracting the response selector, the selector can be tested without actually executing a request

const selector = (response: Response): Promise<FooBar> => response.json(); 

Errors

When using selectors and the selector fails, it will cast a HttpResponseError.

note, if using something like json selector, the response only resolves once, trying to extract json again will cause error

interface HttpResponseError {
  message: string;
  response: Response;
  cause?: Error
}

Json

Async
client.json('/api', {selector}).then(console.log);

conveniences method for json calls, internally calls fetch, but adds Content-Type=application/json to the request header. Also add a default selector if none provided data = await response.json()

Abort

calling client.abort() will cancel all ongoing request that the client has.

when executing an async call, an abort signalopen in new window should be provided with the request arguments

Async
const controller = new AbortController();
const {abort, signal} = controller;
setTimeout(abort, 10);
client.fetch('foo', {signal});

Operators

Operators are handlers that are executed recursively when executing an request and on response (middleware).

persistent

When an operator is set/added to an IHttpClient, the operator will persist on that client until disposed.

import type { ProcessOperator } from '@equinor/fusion-framework-module-http/operators';
const logOperator = (logger: (...args: string[])): ProcessOperator => (x => logger(x));

/** note that a conveniences method for adding headers are included */
client.requestHandler.setHeader('x-trace-id', 'my_foot_print')

/** add a predefined operator */
client.requestHandler.add('log', logOperator(console.log));
client.responseHandler.add('log', logOperator(console.debug));

/** inline operator */
client.requestHandler.add('no-cache', (req) => Object.assign(req, {cache: 'no-cache'});

Subscriptions

IHttpClient exposes observable requests and responses, an observer can never modify the data, which means these properties are meant for things like telemetry.

Request
client.request$.subscribe(
  {
    next: (x: Request) => console.debug(x),
    error: (x: any) => console.error(x),
    complete: () => console.log(`client closed`)
  }
)

React

GitHub package.json version (subfolder of monorepo)
App
import { useHttpClient } from '@equinor/fusion-framework-react-app/http';
const myClient = useHttpClient('my-client');