Home Manual Reference Source

PageProof SDK

Introduction

The PageProof SDK exposes a modular set of components which can be put together to make a unified experience of interacting with the PageProof API across different JavaScript environments.

The SDK is currently only available as a JavaScript library to ensure feature parity between the PageProof app, and any extensions or third party integrations. We currently support the following JavaScript environments.

Throughout the Getting Started section, you will hear mentioned "Adapters". Adapters are smaller components which provide core (or additional) functionality to the SDK. For instance, browser's have a particular method of transmitting HTTP requests, and dealing with the network. Where as Node.js has another completely different method. An adapter may accept additional configuration, specific to the environment it runs on, and in the Getting Started section our examples are primarily focused around the Browser JavaScript environment.

Getting Started

Installation

Start by installing the library using npm. npm is a command line tool typically used for installing dependencies in JavaScript projects - and helps keeps them up to date. Run the following command to install the SDK along with all of it's dependencies.

$ npm install --save @pageproof/sdk

Please contact us about getting access to the npm package, as you will need to be authenticated to install the package.

Constructing a PageProof client

The following assumes you have a clear understanding of using module bundlers (if you plan on building a frontend app with the SDK), or are familiar with the CommonJS module loader in Node.js. If not, check out webpack or this article, which may get you pointed in the right direction.

The PageProof SDK is made to be modular, so there are no "static" "it-just-works" methods. Instead, you need to build a client instance of the main PageProof class. This class takes an array of adapters. These adapters can be configured how to work best in your environment - like configuring how many crypto processes to spawn (in Node.js), or how many Workers to create (in the browser).

Authenticating requests

To make requests to our API, the SDK must know your application's subscription key (referred within request adapters as the key). This key is provided by us to you, and you should usually have two - one the primary key, and another secondary. You have two keys, so that if one key is accidentally made public, you can switch to your secondary key, and send a request for your primary key to be regenerated.

It's also important to note that this key is not used for authenticating as a user within PageProof - it's only used to authorize your requests with our API server. It's how we handle rate limiting & API access. User authentication is handled by you within the SDK.

You also need to provide your application id. This is provided to you by a member of the PageProof team. It allows any sessions generated within the SDK to be separate to the main app session. Without it, the SDK will error, because it will be unable to reliably generate a long-lived session, as logging into app.pageproof.com will override the session.

The following example is how you would configure a request adapter.

import XHRRequestAdapter from '@pageproof/sdk/lib/xhr-request-adapter';

// ...

new XHRRequestAdapter({
  applicationId: '3uQkbQXsinffvnNHPlplx4Uw1sVeWEab',
  endpoint: 'https://managed-api.pageproof.com',
  key: '104882dfa7c44330811612ea100dc7e6',
});

As you can see, the adapter takes an endpoint and a key. For 3rd party developers & integrating PageProof into your own application, the endpoint is https://managed-api.pageproof.com, and the key is your application's subscription key. In production, however, it's best to make your key an environment variable to make it easier to change it later.

Example: Low powered devices

The following example is configured to work nicely on low-powered machines within the browser. It limits itself to only spawning a single crypto worker. This is important, because the more crypto workers that are created, generally means more physical cores are utilized on the machine. For mobile devices, or older desktop machines, crypto operations happen much faster with less workers, where as newer desktops/laptops generally have more cores, meaning you can afford to spawn more.

import PageProof from '@pageproof/sdk';
import XHRRequestAdapter from '@pageproof/sdk/lib/xhr-request-adapter';
import WorkerCryptoAdapter from '@pageproof/sdk/lib/worker-crypto-adapter';

const client = new PageProof({
  adapters: [
    new XHRRequestAdapter({
      endpoint: 'https://managed-api.pageproof.com',
      key: '104882dfa7c44330811612ea100dc7e6',
    }),
    new WorkerCryptoAdapter({
      url: 'https://example.com/dist/pageproof/sdk/lib',
      limit: 1,
    }),
  ],
});

You can see that we've defined a limit within the configuration of the WorkerCryptoAdapter object. You may also notice the url configuration option. The url refers to the location of the worker JavaScript file. This file is located within the package's lib folder. For example, this file may be located at node_modules/@pageproof/sdk/lib/worker.82ceab74.js. The file contains a hash within it's name, which is used to prevent the browser from caching the contents of the worker when you update the version of the SDK. It's important that you make this file available on your own webserver, as it's asynchronously loaded when a new worker is spawned. The SDK contains the file name build-in, so you only need to provide the path to the file, and the SDK will automatically load the correct worker it's compatible with. If you're building a single page app, or any long-lived web page, it's recommended you do not remove old worker files from your webserver until it's safe to do so. For example, deferring the removal of old worker files 24 hours after a new one is put in place. This gives any users still using an old version of the SDK time to update/refresh the page & load the newest version.

Example: Making use of multi-core devices with navigator.hardwareConcurrency

The following example makes use of an API available in newer browsers, in order to figure out how many cores they have available on the device. Check caniuse.com (hardwareconcurrency) for a list of browsers which support this API. The example also falls back to using 2 cores if the API is not supported.

import PageProof from '@pageproof/sdk';
import XHRRequestAdapter from '@pageproof/sdk/lib/xhr-request-adapter';
import WorkerCryptoAdapter from '@pageproof/sdk/lib/worker-crypto-adapter';

const client = new PageProof({
  adapters: [
    new XHRRequestAdapter({
      applicationId: '3uQkbQXsinffvnNHPlplx4Uw1sVeWEab',
      endpoint: 'https://managed-api.pageproof.com',
      key: '104882dfa7c44330811612ea100dc7e6',
    }),
    new WorkerCryptoAdapter({
      url: 'https://example.com/dist/@pageproof/sdk/lib',
      limit: navigator.hardwareConcurrency || 2,
    }),
  ],
});

Legacy browser support

So far these examples have used ES2015 imports to pull through the library and adapters, but we do also make these modules compatible with the <script> element. We don't recommend this, as it exposes the SDK globally within the web page, but it's there for you if you need this (mainly for legacy reasons).

<!DOCTYPE html>
<html>
  <head>
    <title>My App</title>
    <!-- your app's meta tags & stylesheets -->
  </head>
  <body>
    <script src="node_modules/@pageproof/sdk/lib/pageproof.js" defer></script>
    <script src="node_modules/@pageproof/sdk/lib/xhr-request-adapter.js" defer></script>
    <script src="node_modules/@pageproof/sdk/lib/worker-crypto-adapter.js" defer></script>
    <script src="app.js" defer></script>
  </body>
</html>

As you can see, the SDK must be included before your app's main JavaScript file, if you intend to make use of it straight away, however if you have measures in place for proper async loading, this will also work. The modules do not need to be loaded in any particular order, however it's best practice to assume that the core library should be loaded and evaluated before the adapters. Also, pay attention to the defer attribute on the script tags. If you do intend to load these files using <script> elements, this attribute speeds up the loading process of your app, by requesting all the files at once, but evaluating them in order of occurrence.

If you do decide on using <script> elements, and you're not transpiling your code to ES2015+ (with Babel, for example), you will need to add .default after the globals. See example, below, for info.

const PageProof = window.PageProof.default;
const WorkerCryptoAdapter = window.WorkerCryptoAdapter.default;
const XHRRequestAdapter = window.XHRRequestAdapter.default;

const client = new PageProof({
  // ...
});

Adapters

We have a number of adapters, some intended for use within browsers, and some for Node.js.

Name Filename Supported Environment Supported
ClusterCryptoAdapter @pageproof/sdk/lib/adapters/ClusterCryptoAdapter.js Node.js Yes
NodeRequestAdapter @pageproof/sdk/lib/adapters/NodeRequestAdapter.js Node.js Yes
SyncCryptoAdapter @pageproof/sdk/lib/adapters/SyncCryptoAdapter.js Any No
WorkerCryptoAdapter @pageproof/sdk/lib/worker-crypto-adapter.js Browser Yes
XHRRequestAdapter @pageproof/sdk/lib/xhr-request-adapter.js Browser Yes

The SyncCryptoAdapter is the only one we do not support, as it is a blocking adapter. You should not use this in production - it's only intended to be used within custom adapters you may write for a specific (officially unsupported) JavaScript environment.

Also note, the browser adapters have a slightly different filename/path, this is because these files come pre-minified, with UMD support (CommonJS, ES2015+ import/export, and <script>).

APIs

Once you have a PageProof client, you're ready to interact with the PageProof API, like logging into your account. All of our APIs are compartmentalized, to make it easier to find the API you're looking for.

These are the different APIs we currently support through the SDK.

Name Namespace Description Admin-only Stability
Accounts API accounts Used to create accounts, activate and login. No Stable
Comments API comments Used to load comments. No Stable
Dashboard API dashboard Used to load the authenticated user's dashboard & search for/filter proofs. No Stable
Files API files Used to upload & manage files. No Stable
Proofs API proofs Used to create & manage proofs. No Stable
Proofs Owners API proofs.owners Used to add & remove owners from proofs. No Stable
Users API users Used to invite and find users in the system. No Stable
Workflows API workflows Used to create & manage workflows and workflow templates. No Stable
Admin Team Users API admin.team.users Administer users in a team or enterprise account. Yes Experimental

The "admin-only" APIs are exposed through the PageProof.admin method. These APIs are different because the SDK only allows access to this subset of APIs if the current user is an administrator of a team/enterprise account. Currently, all admin-only APIs are experimental.

To explain what the "namespace" is used for, take this example. You would like to find a user in PageProof with a particular email. That would mean using the "Users API", which has a namespace of users. So that would look like this;

const client = new PageProof({ /* ... */ });

const user = await client.users.byEmail('user@example.com');
console.log(user); // User { id: "UDLCS204C9O9JN6T", name: "John Smith", ... }

There's also a list of APIs in the docs sidebar, under the section "api".

Authenticating as a user/logging in

Only some of the PageProof APIs are available publicly. Most, however, require an authenticated user to make them. Authenticating as a user makes use of the Accounts API. Here's an example...

const client = new PageProof({ /* ... */ });

try {
  await client.accounts.login('user@example.com', '<password>');
} catch(err) {
  console.error('Whoops, the credentials were incorrect...', err);
}

// later on...
const proofs = await client.dashboard.sent();
console.log('My proofs...', proofs);

However if you left the code just like that, you would end up having to login every time you loaded the page - not ideal. In the browser there's an API called localStorage, which allows for persistent data to be stored & recovered at a later stage. The implementation is up to you, however serializing the session object as JSON and storing it in localStorage is recommended for most client side applications. Check out this example;

const session = await client.accounts.login('user@example.com', '<password>');
const sessionJSON = JSON.stringify(session);
localStorage.setItem('com.pageproof.session', sessionJSON);
const sessionJSON = localStorage.getItem('com.pageproof.session');
if (sessionJSON !== null) {
  const session = JSON.parse(sessionJSON);
  client.setSession(session);
}

You may notice we're making use of a method called setSession within our PageProof client. This method is automatically called when calling the accounts.login method, but if the user refreshes, we need to remind the client what the current session is.

NEVER send the session object over the network. The Session object contains the user's private keys. Sharing the private keys with anybody, directly or even indirectly (over unencrypted traffic, for example), gives anybody the ability to access & decrypt all proofs you have access to, including those you have been invited to.