@civic/gateway-client-core
TypeScript icon, indicating that this package has built-in type declarations

1.1.1 • Public • Published

Civic Gateway Client Core

Overview

The Civic gateway-client-core library is a state-management library for managing interactions with the Civic Pass system. It listens to inputs from sources such as Civic gatekeeper, on-chain events, Civic Pass data collection iframe, and orchestrates calls to the Civic Gatekeeper API, outputting the gatewayStatus and flowParameters that can be used to present a Civic Pass UI to the user and move forward with pass creation and refresh.

Getting started

npm run build builds the library to dist, generating three files:

  • dist/civic-gateway-client-core.cjs.js A CommonJS bundle, suitable for use in Node.js, that requires the external dependency. This corresponds to the "main" field in package.json
  • dist/civic-gateway-client-core.esm.js an ES module bundle, suitable for use in other people's libraries and applications, that imports the external dependency. This corresponds to the "module" field in package.json
  • dist/civic-gateway-client-core.umd.js a UMD build, suitable for use in any environment (including the browser, as a <script> tag), that includes the external dependency. This corresponds to the "browser" field in package.json

npm run dev builds the library, then keeps rebuilding it whenever the source files change using rollup-watch.

npm test builds the library, then tests it.

How it works

The library uses zustand for inernal state management, where the state is divided into separate concerns:

inputs: ClientCoreInput;
internal: ClientCoreInternal;
output?: ClientCoreOutput;
functions: {
reset: () => void;
};

The high-level flow is that:

  • inputs get updated in state via external listeners and changes in the input parameters (on-chain state, gatekeeper record state, events from civic-pass iframe etc)
  • the internal.status gets calculated based on the current state of all the inputs
  • the internal orchestrator subscribes to status events and for specific status events it performs some orchestration, e.g. requesting a gateway token from the gatekeeper-api when it receives a data-collection payload from civic-pass
  • outputs are derived from the current internal state and are used by the instantiater of the gateway-client-core to show a status to the user

ClientCoreInput

This represents the state of different external inputs to the client-core:

civicSign: GatewayInput<CivicSignEventTypeRequestMessage, ChainError>;
civicPass: GatewayInput<CivicPassMessageResponse>;
gatewayToken: GatewayInput<GatewayToken>;
gatekeeperRecord: GatewayInput<GatekeeperRecordResponse>;
parameters: GatewayClientParameters | null;

civicSign

civicSign events are sent and received using postMessage and can be considered 'outside' of the gatewayStatus flow, in that a signature request or did request can be received at any point in any flow. CivicSign events are subscribed to in the listenerManager and handled by 'remoteSign'

civicPass

civicPass is an external frontend application that collects data and posts events that gateway-client-core listens to. These events are subscribed to in the listener manager and normally cause a change in the computed gatewayStatus that in turn can trigger orchestration flows

gatewayToken

gatewayToken represents the on-chain state, where the input chainImplementation is used to query for and listen to token events. Changes to gatewayToken will also trigger gatewayStatus changes and trigger orchestration flows

gatekeeperRecord

gatekeeperRecord represents the civic-gatekeeper-api view of a pass: civic checks are applied on requests to the gatekeeper and can result in token rejections. The orchestrator's main function is to call the gatekeeper-api during different orchestration flows (issuance, refresh etc.) using data collected and provided by civic-pass events.

parameters

The parameters represent the instantiation parameters of gateway-client-core and represent options for things like wallet address, gatekeeper-network etc. that remain constant during a flow.

ClientCoreInternal

export type ClientCoreOutput = {
  gatewayStatus: GatewayStatus;
  gatewayToken?: GatewayToken;
  flowParameters: FlowParameters | null;
  pendingRequests: PendingPayload | undefined;
  flowState?: {
    status?: FlowStatus;
    userInteraction: UserInteraction;
  };
};

The outputs represent states that the instantiator of the gateway-client-core would be interested in:

  • gatewayStatus is the distillation of all the current inputs and can also be thought of as the overall status, representing a derived state from on-chain status, data-collection and gatekeeper record status. This can be used by UI components such as the Civic IdentityButton to show the user what the current state of their pass is
  • gatewayToken is the on-chain token object
  • flowParameters are used to send to a frontend, representing the different action, state and parameters that the frontend needs to display a flow to the user. They can be merged into e.g. iframe GET parameters
  • pendingRequests: applicable to the custom PII-sharing flow only: a pending request means that the instantiator (normally a dApp) would pass this id to some external system to do their own validation on in order to issue a civic pass
  • flowState: indicates whether the flow is in progress or complete etc. so that the instantiator of gateway-client-core can decide whether to show a UI to the user or not.

Dynamic inputs

Most of the input parameters to the client-core are static, i.e. any change in them should cause state to be reset and a new instance of the client-core to be used. However, there are some dynamic inputs that should not cause a reset but instead should trigger a new flow.

forceRequireRefresh

In order to allow a dApp to implement custom refresh logic, a boolean flag forceRequireRefresh can be set by calling the updateDynamicParameters() function on a gateway-client-core instance.

This flag will only cause a flow if an ACTIVE gatewayToken already exists for a given wallet. Setting it will cause the client-core to view the currrent gatewayToken as expired, and thus trigger set the gatewayStatus to REFRESH_TOKEN_REQUIRED, and to guide the user through a refresh-token flow. When the token expiry is updated to a value in the future, then the flow is viewed as finished and the gatewayStatus will once again be ACTIVE.

UI integration

The core offers optional UI state management using output variables that can be used to control a remote frontend, and providing input methods that can be wired up to user interface elements.

UI Inputs

The UI input consists of 3 methods that are exposed directly on the gateway-client-core instance:

  • onShow: () => void: will set the Civic Pass frontend visibility to true and start or resume a user-flow, normally connected to the Civic Identity button, or other UI element that the user starts the Civic Pass flow with
  • onHide: () => void: will set the Civic Pass frontend visibily to false. Normally connected to the 'close' button on the frontend, if applicable
  • onLoad: () => void: to trigger when the frontend loads, i.e. on the iframe 'onLoad' method

UI Output

The UI output takes the form of state variables that can be wired to UI elements to show different states:

{
  isVisible: boolean; // the frontend visibility
  url?: string; // the URL to load frontend content
  isLoading: boolean; // current loading status
  isLoaded: boolean; // current loaded status
}

UI Example

  const inputs = <define core inputs>;
  const [clientCoreOutput, setClientCoreOutput] = useState<ReactClientCoreOutput | undefined>(undefined);
  
  const onOutputChange = (output: ClientCoreOutput | undefined) => {
    setClientCoreOutput(output);
  };
  const core = GatewayClientCore.getSingleInstance({ ...inputs, onOutputChange});

  {clientCoreOutput?.ui?.url && (
      <IframeWrapper>
        {ui?.output?.isLoading && <LoaderOverlay />}
        <CloseButton onClick={() => core?.ui?.onHide()} />
        <iframe
          src={clientCoreOutput?.ui?.url}
          style={{
            pointerEvents: ui?.output?.isLoaded ? 'auto' : 'none', // disables user input during loading
          }}
          onLoad={() => {
            core?.ui?.onLoad?.();
          }}
        />
      </IframeWrapper>

      <IdentityButton onClick={() => core?.ui?.onShow()}>
  )}

Error flow handling

Civic-sign errors

If the user rejects a transaction, or the chainImplementation.proveWalletOwnership() function failes for any reason, the gateway-client catches the error and uses the civic-sign remote post message channel to send back the error to the requester so that it can be handled on that side, rather than in the gateway-client.

Transaction send errors

In the 'issuanceClientSends-resume-requested' case:

  1. the gatekeeper tells the client that a transaction can be retried by including the flag canRequestFreshTx: true in the response
  2. the client changes state to ISSUANCE_CLIENT_SENDS_START_NEW_TX or REFRESH_CLIENT_SENDS_START_NEW_TX
  3. this change of status causes a new iframe screen to load: startPreApprovedTransaction
  4. the user is asked for POWO which successfully sent from the iframe in an issuance/success/payload civicpass event
  5. the computeIssuanceStartPreApprovedTransaction state fires and calculates a new status of ISSUANCE_CLIENT_SENDS_REQUEST_NEW_TX
  6. this causes the iframe to show the usual 'signTransaction' screen (with fees etc.)
  7. the orchestrator detects the ISSUANCE_CLIENT_SENDS_REQUEST_NEW_TX state and calls a new function on the issuance/refresh services fetchFreshTransaction()
  8. this calls the gatekeeperClient using a new client method fetchFreshTransaction({ payer, payload }) which updates the gatekeeperRecord.received value in state
  9. the computeIssuanceInReview() state function fires and we're back in the normal 'client-sends' flow waiting to send the on-chain transaction

In the 'retries-exhausted' case:

  1. the user goes through the normal client-sends flow until they get to the point where they have a transaction to sign/send
  2. the transaction sign/send fails (for whatever reason, either user cancels, not enough funds, or something goes wrong sending)
  3. the client reties with the same transaction until the 'clientSendsMaxRetries' limit is hit
  4. the client goes into ISSUANCE_CLIENT_SENDS_REQUEST_NEW_TX status
  5. this causes the iframe to show the usual 'signTransaction' screen (with fees etc.)
  6. the orchestrator detects the ISSUANCE_CLIENT_SENDS_REQUEST_NEW_TX state and calls a new function on the issuance/refresh services fetchFreshTransaction()
  7. this calls the gatekeeperClient using a new client method fetchFreshTransaction({ payer, payload }) which updates the gatekeeperRecord.received value in state
  8. the computeIssuanceInReview() state function fires and we're back in the normal 'client-sends' flow waiting to send the on-chain transaction

Readme

Keywords

none

Package Sidebar

Install

npm i @civic/gateway-client-core

Weekly Downloads

204

Version

1.1.1

License

MIT

Unpacked Size

2.05 MB

Total Files

187

Last publish

Collaborators

  • pedroapfilho
  • callforsanity
  • jp-civic
  • chriteixeira
  • daneel
  • mitchcivic
  • dankelleher
  • jonthepilot
  • civicfinance
  • kevinhcolgan
  • flippiescholtz
  • tyronemichael
  • lucmir