pryv
    TypeScript icon, indicating that this package has built-in type declarations

    2.2.0 • Public • Published

    JavaScript library for Pryv.io

    This JavaScript library is meant to facilitate writing NodeJS and browser apps for a Pryv.io platform, it follows the Pryv.io App Guidelines.

    Contribute

    Prerequisites: Node 12

    • Setup: npm run setup

    • Build pryv.js library for browsers: npm run build, the result is published in ./dist

    • Build documentation: npm run doc, the result is published in ./dist/docs

      Note: as per v2.1.7 jsdoc dev dependency has been removed from package.json .. it should be installed with npm install jsoc --dev

    • Node Tests: npm run test

    • Coverage: npm run cover, the result is visible in ./coverage

    • Browser tests: build, then npm run webserver and open https://l.rec.la:9443/tests/browser-tests.html?pryvServiceInfoUrl=https://zouzou.com/service/info

    • Update on CDN: After running setup and build scripts, run npm run gh-pages ${COMMIT_MESSAGE}. If this fails, run npm run clear to rebuild a fresh dist/ folder

    Usage

    Table of Contents

    Import

    Browser

    <script src="https://api.pryv.com/lib-js/pryv.js"></script>

    Others distributions for browsers & extensions:

    Example on code pen:

    Node.js

    Install with: npm install pryv --save

    const Pryv = require('pryv');

    Obtaining a Pryv.Connection

    A connection is an authenticated link to a Pryv.io account.

    Using an API endpoint

    The format of the API endpoint can be found in your platform's service information under the api property. The most frequent one has the following format: https://{token}@{api-endpoint}

    const apiEndpoint = 'https://ck6bwmcar00041ep87c8ujf90@drtom.pryv.me';
    const connection = new Pryv.Connection(apiEndpoint);

    Using a Username & Token (knowing the service information URL)

    const service = new Pryv.Service('https://reg.pryv.me/service/info');
    const apiEndpoint = await service.apiEndpointFor(username, token);
    const connection = new Pryv.Connection(apiEndpoint);

    Within a WebPage with a login button

    The following code is an implementation of the Pryv.io Authentication process.

    <!doctype html>
    <html>
    <head>
      <title>Pryv - Javascript Lib</title>
      <script src="https://api.pryv.com/lib-js/pryv.js"></script>
    </head>
    <body>
      <span id="pryv-button"></span>
      <script>
        var connection = null;
    
        var authSettings = {
          spanButtonID: 'pryv-button', // span id the DOM that will be replaced by the Service specific button
          onStateChange: pryvAuthStateChange, // event Listener for Authentication steps
          authRequest: { // See: https://api.pryv.com/reference/#auth-request
            requestingAppId: 'lib-js-test',
            languageCode: 'fr', // optional (default english)
            requestedPermissions: [
              {
                streamId: 'test',
                defaultName: 'test',
                level: 'manage'
              }
            ],
            clientData: {
              'app-web-auth:description': {
                'type': 'note/txt', 'content': 'This is a consent message.'
              }
            },
            // referer: 'my test with lib-js', // optional string to track registration source
          }
        };
    
        function pryvAuthStateChange(state) { // called each time the authentication state changed
          console.log('##pryvAuthStateChange', state);
          if (state.id === Pryv.Auth.AuthStates.AUTHORIZED) {
            connection = new Pryv.Connection(state.apiEndpoint);
            logToConsole('# Browser succeeded for user ' + connection.apiEndpoint);
          }
          if (state.id === Pryv.Auth.AuthStates.SIGNOUT) {
            connection = null;
            logToConsole('# Logout');
          }
      }
        var serviceInfoUrl = 'https://api.pryv.com/lib-js/demos/service-info.json';
        (async function () {
          var service = await Pryv.Auth.setupAuth(authSettings, serviceInfoUrl);
        })();
      </script>
    </body>
    </html>

    Fetch access info

    Implementation of access info.

    const apiEndpoint = 'https://ck6bwmcar00041ep87c8ujf90@drtom.pryv.me';
    const connection = new Pryv.Connection(apiEndpoint);
    const accessInfo = await connection.accessInfo();

    Using Service.login() (trusted apps only)

    auth.login reference

    const serviceInfoUrl = 'https://reg.pryv.me/service/info';
    const appId = 'lib-js-sample';
    const service = new Pryv.Service(serviceInfoUrl);
    const connection = await service.login(username, password, appId);

    API calls

    Api calls are based on the batch call specifications: Call batch API reference

    const apiCalls = [
      {
        "method": "streams.create",
        "params": { "id": "heart", "name": "Heart" }
      },
      {
        "method": "events.create",
        "params": { "time": 1385046854.282, "streamId": "heart", "type": "frequency/bpm", "content": 90 }
      },
      {
        "method": "events.create",
        "params": { "time": 1385046854.283, "streamId": "heart", "type": "frequency/bpm", "content": 120 }
      }
    ]
    
    try {
      const result = await connection.api(apiCalls)
    } catch (e) {
      // handle error
    }

    Advanced usage of API calls with optional individual result and progress callbacks

    let count = 0;
    // the following will be called on each API method result it was provided for
    function handleResult(result) { console.log('Got result ' + count++ + ': ' + JSON.stringify(result)); }
    
    function progress(percentage) { console.log('Processed: ' + percentage + '%'); }
    
    const apiCalls = [
      {
        method: 'streams.create',
        params: { id: 'heart', name: 'Heart' }
      },
      {
        method: 'events.create',
        params: { time: 1385046854.282, streamId: 'heart', type: 'frequency/bpm', content: 90 },
        handleResult: handleResult
      },
      {
        method: 'events.create',
        params: { time: 1385046854.283, streamId: 'heart', type: 'frequency/bpm', content: 120 },
        handleResult: handleResult
      }
    ]
    
    try {
      const result = await connection.api(apiCalls, progress)
    } catch (e) {
      // handle error
    }

    Get Events Streamed

    When events.get will provide a large result set, it is recommended to use a method that streams the result instead of the batch API call.

    Pryv.Connection.getEventsStreamed() parses the response JSON as soon as data is available and calls the forEachEvent() callback on each event object.

    The callback is meant to store the events data, as the function does not return the API call result, which could overflow memory in case of JSON deserialization of a very large data set. Instead, the function returns an events count and eventually event deletions count as well as the common metadata.

    Example:

    const now = (new Date()).getTime() / 1000;
    const queryParams = { fromTime: 0, toTime: now, limit: 10000};
    const events = [];
    function forEachEvent(event) {
      events.push(event);
    }
    
    try {
      const result = await connection.getEventsStreamed(queryParams, forEachEvent);
    } catch (e) {
      // handle error
    }

    result:

    {
      eventsCount: 10000,
      meta:
      {
          apiVersion: '1.4.26',
          serverTime: 1580728336.864,
          serial: '2019061301'
      }
    }

    Example with Includes deletion:

    const now = (new Date()).getTime() / 1000;
    const queryParams = { fromTime: 0, toTime: now, includeDeletions: true, modifiedSince: 0};
    const events = [];
    function forEachEvent(event) {
      events.push(event);
      // events with .deleted or/and .trashed properties can be tracked here
    }
    
    try {
      const result = await connection.getEventsStreamed(queryParams, forEachEvent);
    } catch (e) {
      // handle error
    }

    result:

    {
      eventDeletionsCount: 150,
      eventsCount: 10000,
      meta:
      {
          apiVersion: '1.4.26',
          serverTime: 1580728336.864,
          serial: '2019061301'
      }
    }

    Events with Attachments

    This shortcut allows to create an event with an attachment in a single API call.

    Node.js

    const filePath = './test/my_image.png';
    const result = await connection.createEventWithFile(
      {
        type: 'picture/attached',
        streamId: 'data'
      },
      filePath
    );

    or from a Buffer

    const filePath = './test/my_image.png';
    const bufferData = fs.readFileSync(filePath);
    
    const result = await connection.createEventWithFileFromBuffer(
      {
        type: 'picture/attached',
        streamId: 'data'
      },
      bufferData,
      'my_image.png' // filename
    );

    Browser

    From an Input field

    <input type="file" id="file-upload"><button onClick='uploadFile()'>Save Value</button>
    
    <script>
      var formData = new FormData();
      formData.append(
        'file0',
        document.getElementById('create-file').files[0]
    ) ;
    
      connection.createEventWithFormData(
        {
          type: 'file/attached',
          streamId: 'test'
        },
        formData)
        .then(function (res, err) {
          // handle result here
        }
      );
    </script>

    Progamatically created content:

    var formData = new FormData();
    var blob = new Blob(
      ['Hello'],
      { type: "text/txt" }
    );
    formData.append("webmasterfile", blob);
    
    connect.createEventWithFormData(
      {
        type: 'file/attached',
        streamId: 'data'
      },
      formData)
      .then(function (res, err) {
        // handle result here
      }
    );
    
    // -- alternative with a filename
    
    connect.createEventWithFileFromBuffer(
      {
        type: 'file/attached',
        streamId: 'data'
      },
      blob, 'filename.txt')  // here we can directly use the blob
      .then(function (res, err) {
        // handle result here
      }
    );
    

    High Frequency Events

    Reference: https://api.pryv.com/reference/#hf-events

    function generateSerie() {
      const serie = [];
      for (let t = 0; t < 100000, t++) { // t will be the deltaTime in seconds
        serie.push([t, Math.sin(t/1000)]);
      }
      return serie;
    }
    const pointsA = generateSerie();
    const pointsB = generateSerie();
    
    function postHFData(points) { // must return a Promise
       return async function (result) { // will be called each time an HF event is created
        return await connection.addPointsToHFEvent(result.event.id, ['deltaTime', 'value'], points);
      }
    }
    
    const apiCalls = [
      {
        method: 'streams.create',
        params: { id: 'signal1', name: 'Signal1' }
      },
      {
        method: 'streams.create',
        params: { id: 'signal2', name: 'Signal2' }
      },
      {
        method: 'events.create',
        params: { streamId: 'signal1', type: 'serie:frequency/bpm' },
        handleResult: postHFData(pointsA)
      },
      {
        method: 'events.create',
        params: { streamId: 'signal2', type: 'serie:frequency/bpm' },
        handleResult: postHFData(pointsB)
      }
    ]
    
    try {
      const result = await connection.api(apiCalls)
    } catch (e) {
      // handle error
    }

    Service Information and assets

    A Pryv.io deployment is a unique "Service", as an example Pryv Lab is a service, deployed on the pryv.me domain name.

    It relies on the content of a service information configuration, See: Service Information API reference

    Pryv.Service

    Exposes tools to interact with Pryv.io at a "Platform" level.

    Initizalization with a service info URL
    const service = new Pryv.Service('https://reg.pryv.me/service/info');
    Initialization with the content of a service info configuration

    Service information properties can be overriden with specific values. This might be useful to test new designs on production platforms.

    const serviceInfoUrl = 'https://reg.pryv.me/service/info';
    const serviceCustomizations = {
      name: 'Pryv Lab 2',
      assets: {
        definitions: 'https://pryv.github.io/assets-pryv.me/index.json'
      }
    }
    const service = new Pryv.Service(serviceInfoUrl, serviceCustomizations);
    Usage of Pryv.Service.

    See: Pryv.Service for more details

    • service.info() - returns the content of the serviceInfo in a Promise

      // example: get the name of the platform
      const serviceName = await service.info().name
    • service.infoSync(): returns the cached content of the serviceInfo, requires service.info() to be called first.

    • service.apiEndpointFor(username, token) Will return the corresponding API endpoint for the provided credentials, token can be omitted.

    Pryv.Browser & Visual assets

    Pryv.Browser - retrieve serviceInfo from query URL

    A single Web App might need to be run on different Pryv.io platforms. This is the case of most Pryv.io demonstrators.

    The corresponding Pryv.io platform can be specified by providing the Service Information URL as query parameter pryvServiceInfoUrl as per the Pryv App Guidelines. It can be extracted using Pryv.Browser.serviceInfoFromUrl() .

    Example of usage for web App with the url https://api.pryv.com/app-web-access/?pryvServiceInfoUrl=https://reg.pryv.me/service/info

    let defaultServiceInfoUrl = 'https://reg.pryv.me/service/info';
    // if present override serviceInfoURL from URL query param "?pryvServiceInfoUrl=.."
    serviceInfoUrl = Pryv.Browser.serviceInfoFromUrl() || defaultServiceInfoUrl;
    
    (async function () {
    	var service = await Pryv.Auth.setupAuth(authSettings, serviceInfoUrl, serviceCustomizations);
    })();

    Visual assets

    To customize assets and visuals refer to: pryv.me assets github

    To customize the Sign in Button refer to: sign in button in pryv.me assets

    (service.assets()).setAllDefaults(): loads the css and favicon properties of assets definitions.

    (async function () {
      const service = await Pryv.Auth.setupAuth(authSettings, serviceInfoUrl);
      (await service.assets()).setAllDefaults(); // will load the default Favicon and CSS for this platform
    })();

    Customize Auth process

    You can customize the authentication process at different levels:

    1. Using a custom login button to launch the Pryv.io Authentication process.
    2. Using a custom UI for the Pryv.io Authentication process, including the flow of app-web-auth3.

    Using a custom login button

    You will need to implement a class that instanciates an AuthController object and implements a few methods. We will go through this guide using the Browser's default Login Button provided with this library as example.

    Initialization

    You should provide it authSettings (See Obtain a Pryv.Connection) and an instance of Service at initialization. As this phase might contain asynchronous calls, we like to split it between the constructor and an async init() function. In particular, you will need to instanciate an AuthController object.

    constructor(authSettings, service) {
      this.authSettings = authSettings;
      this.service = service;
      this.serviceInfo = service.infoSync();
    }
    
    async init () {
      // initialize button visuals
      // ...
    
      // set cookie key for authorization data - browser only
      this._cookieKey = 'pryv-libjs-' + this.authSettings.authRequest.requestingAppId;
    
      // initialize controller
      this.auth = new AuthController(this.authSettings, this.service, this);
      await this.auth.init();
    }
    Authorization data

    At initialization, the AuthController will attempt to fetch some persisted authorization credentials, using LoginButton.getAuthorizationData(). In the browser case, we are using a client-side cookie. For other frameworks, use an appropriate secure storage.

    getAuthorizationData () {
      return Cookies.get(this._cookieKey);
    }
    Authentication lifecycle

    The authentication process implementation on the frontend is defined in the following states:

    1. Loading: while the visual assets are loading
    2. Initialized: visuals assets are loaded, or when polling concludes with Result: Refused
    3. Need sign in: From the response of the auth request through polling
    4. Authorized: When polling concludes with Result: Accepted
    5. Sign out: When the user triggers a deletion of the client-side authorization credentials, usually by clicking the button after being signed in
    6. Error: See message for more information

    You will need to provide a function to act depending on the state. The statesNEED_SIGNIN and AUTHORIZED contain the same fields as the auth process polling responses. LOADING, INITIALIZED and SIGNOUT only contain status. The ERROR state carries a message property.

    async onStateChange (state) {
      switch (state.status) {
        case AuthStates.LOADING:
          this.text = getLoadingMessage(this);
          break;
        case AuthStates.INITIALIZED:
          this.text = getInitializedMessage(this, this.serviceInfo.name);
          break;
        case AuthStates.NEED_SIGNIN:
          const loginUrl = state.authUrl || state.url; // .url is deprecated
          if (this.authSettings.authRequest.returnURL) { // open on same page (no Popup)
            location.href = loginUrl;
            return;
          } else {
            startLoginScreen(this, loginUrl);
          }
          break;
        case AuthStates.AUTHORIZED:
          this.text = state.username;
          this.saveAuthorizationData({
            apiEndpoint: state.apiEndpoint,
            username: state.username
          });
          break;
        case AuthStates.SIGNOUT:
          const message = this.messages.SIGNOUT_CONFIRM ? this.messages.SIGNOUT_CONFIRM : 'Logout ?';
          if (confirm(message)) {
            this.deleteAuthorizationData();
            this.auth.init();
          }
          break;
        case AuthStates.ERROR:
          this.text = getErrorMessage(this, state.message);
          break;
        default:
          console.log('WARNING Unhandled state for Login: ' + state.status);
      }
      if (this.loginButtonText) {
        this.loginButtonText.innerHTML = this.text;
      }
    }
    Button actions

    The button actions should be handled by the AuthController in the following way:

    // LoginButton.js
    onClick () {
      this.auth.handleClick();
    }
    // AuthController.js
    async handleClick () {
      if (isAuthorized.call(this)) {
        this.state = { status: AuthStates.SIGNOUT };
      } else if (isInitialized.call(this)) {
        this.startAuthRequest();
      } else if (isNeedSignIn.call(this)) {
        // reopen popup
        this.state = this.state;
      } else {
        console.log('Unhandled action in "handleClick()" for status:', this.state.status);
      }
    }
    Custom button usage

    You must then provide this class as following:

    let service = await Pryv.Auth.setupAuth(
      authSettings, // See https://github.com/pryv/lib-js#within-a-webpage-with-a-login-button
      serviceInfoUrl,
      serviceCustomizations,
      MyLoginButton,
    );

    You will find a working example in ./web-demos/custom-login-button.html. You can run this code at https://api.pryv.com/lib-js/demos/custom-login-button.html.

    Follow the instructions below on how to run these examples locally.

    For a more advanced scenario, you can check the default button implementation at ./src/Browser/LoginButton.js.

    Redirect user to the authentication page

    There is a possibility that you would like to register the user in another page. You can check the ./web-demos/auth-with-redirection.html example. Also you can try the same code in https://api.pryv.com/lib-js/demos/auth-with-redirection.html. Here is the explanation how to launch web-demos locally

    Launch web demos locally

    You can find html examples in the ./web-demos directory. You can launch them in 2 ways:

    1. using rec-la that allows to run your code with a valid SSL certificate (this requires to have run npm run build prior). To launch the server you simply need to run:

      npm run webserver

      and open an example with the following URL https://l.rec.la:9443/demos/EXAMPLE_NAME.html, like: https://l.rec.la:9443/demos/auth.html

    2. as a simple html file (service information must be passed as JSON to avoid CORS problem).

    Change Log

    2.2.0

    • Added TypeScript typings – contribution from @ovesco

    2.1.7

    • Removed JSDOC dev dependency for security reason

    2.1.0

    • UI separated from the Authentication logic
    • Extendable UI feature was added

    2.0.3

    • Added Connection.username()
    • Various dependencies upgrades
    • Fixing Origin header in Browser distribution

    2.0.1 Initial Release

    Keywords

    Install

    npm i pryv

    DownloadsWeekly Downloads

    16

    Version

    2.2.0

    License

    BSD-3-Clause

    Unpacked Size

    169 kB

    Total Files

    44

    Last publish

    Collaborators

    • perki
    • kebetsi
    • sgoumaz