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.
- Node.js (v0.12 and higher)
- Browsers (Chrome, Firefox, IE, Safari)
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.