Skip to content
This repository has been archived by the owner on Oct 6, 2021. It is now read-only.

Latest commit

 

History

History
758 lines (626 loc) · 20.6 KB

README.MD

File metadata and controls

758 lines (626 loc) · 20.6 KB

Kloudless JavaScript SDK

JavaScript SDK for the Kloudless API.

Kloudless enables apps to integrate with several third-party services with a single integration.

Sign up for a Kloudless Application first before using this SDK.

For convenience, the SDK Object will be referred as kloudless throughout this document.

Table of Contents

Installation

  • Using Node.js or webpack:

    npm install @kloudless/kloudless
    yarn add @kloudless/kloudless
    

    For an ES6 / babel environment:

    import kloudless from '@kloudless/kloudless';

    Or in Node.js environment:

    const kloudless = require('@kloudless/kloudless');
  • Alternatively you can also use this script tag for vanilla browser JS environment:

    <script type="text/javascript"
    src="https://static-cdn.kloudless.com/p/platform/sdk/kloudless.js"></script>

    The SDK Object will be available at window.Kloudless.sdk

Full SDK Reference

The SDK Reference contains the full description for each property and method in the SDK. However, we recommend you proceed through the steps in the Getting Started section below first.

Getting Started

Initializing Account Object

Most Kloudless API endpoints require connecting to upstream service account first. You can start by navigating to API Explorer and connect a storage service account.

Copy the Bearer Token in the text input box after connecting an account, and use it to initialize an Account object:

import kloudless from '@kloudless/kloudless';

const token = BEARER_TOKEN;

const account = new kloudless.Account({
  token
});

// use getDetail() to retrieve account details like email and service name.
account.getDetail().then(() => {
  // `data` property is available after calling getDetail()
  console.log('Account email:', account.data.account);
});

Making API Requests

Once the object is created, you can use it to make API Requests.

For example, you can use get() to make GET requests, it returns a Promise that will be resolved once the API response is returned.

// get root folder metadata
account.get({
  url: 'storage/folders/root/'
}).then((response) => {
  // API response is available at `data` property
  const { data } = response;
  console.log('Can create folders in root folder: ', data.can_create_folders);
});

Make a POST request with post() and put request body in data property:

// create a new folder on root folder
account.post({
  // full URL is https://api.kloudless.com/v1/accounts/me/storage/folders
  url: 'storage/folders',
  data: {
    parent_id: 'root',
    name: 'New Folder Name'
  }
}).then((response) => {
  console.log(`Folder created: ${response.data.name}`);
});

Similarly, you can also use post(), patch(), and delete() to make corresponding http requests.

Integrating OAuth Flow

In practice, your app will require users to connect their upstream service account in order to access upstream data and make API request on their behalf.

For example, users will need to connect their Google Drive account, so that your app can make requests to Kloudless Storage API and retrieve users' storage data.

The SDK provides helper methods based on your environment to help users authorize their accounts using OAuth 2.0.

  • Browsers:

    let account;
    
    // open a popup to let user connect a storage service account
    kloudless.connectAccount({
      scope: 'any.storage',
      appId: YOUR_APP_ID,
    }).then((connectedAccount) => {
      account = connectedAccount;
      // you can access account's detail via `data` property
      const { data } = account;
      console.log(`${data.service_name}: ${data.account}`);
    }).then((response) => {
      console.log(response.data);
    });
  • Node.js: (Using Express as an example):

    const express = require('express');
    const kloudless = require('@kloudless/kloudless');
    
    const app = express();
    
    // oauth state should be stored in a session, but to simplify this example,
    // we just use a variable here
    let oAuthState;
    
    let account;
    
    // connect a storage service account
    app.get('/', (req, res) => {
      // get OAuth url
      const { url, state } = kloudless.getAuthorizationUrl({
        redirectUri: 'http://localhost:8080/callback',
        scope: 'any.storage',
        appId:  YOUR_APP_ID,
      });
      oAuthState = state;
      // redirect users to this url to login their storage account
      res.redirect(url);
    });
    
    // get access token from callback url
    app.get('/callback', (req, res) => {
      // create the Account Object from callback url and its query params
      kloudless.completeAuth({
        state: oAuthState,
        redirectUri: 'http://localhost:8080/callback',
        oauthCallbackUrl: req.originalUrl,
        appId: YOUR_APP_ID,
        apiKey: YOUR_API_KEY,
      }).then((token) => {
        account = new kloudless.Account({ token });
        account.getDetail().then(() => {
          // you can access account's detail via `data` property
          const { data } = account;
          res.send(`${data.service_name}: ${data.account}`);
        })
      });
    });
    
    app.listen(8080);
    console.log('Server running at http://localhost:8080');

Please refer to documentation for detailed usage of each method in the examples above.

You can also read Keep Returned Bearer Tokens and Verifying Account Bearer Tokens for best practices to manage returned Account object or Bearer Token.

Handling API Responses

The SDK will wrap API responses into response objects. Depending on what is in response data, the SDK will create different types of response objects.

data property

You can always get the original response from API from the data property, regardless of which type of response is returned.

// get file metadata
account.get({
  url: `storage/files/${FILE_ID}/`
}).then((resource) => {
  // print details of this file
  const { data } = resource;
  console.log(
    `Filename: ${data.name}\n`
    + `Created: ${data.created}\n`
    + `Modified: ${data.modified}\n`
    + `Size: ${data.size} bytes`
  );
});

Resource

A resource represents any Object with an identifier. For example: Files and folders in the Storage API, calendar events in the Calendar API, and events in Events API.

// get file metadata
account.get({
  url: `storage/files/${FILE_ID}/`
}).then((resource) => {
  // update filename, notice that we don't need to set URL here
  return resource.patch({
    data: {
      name: NEW_FILE_NAME
    }
  });
}).then((resource) => {
  console.log(`New filename: ${resource.data.name}`);
});

Resource List

A resource list represents a list of resources. For example: Contents of a folder, list of calendars under an account, calendar events under a calendar, or any response that has property "type"="object_list".

Resource lists are represented as ResourceList objects. The objects properties contains a list of Resource objects.

// list all calendars of this calendar account
account.get({
  url: 'cal/calendars'
}).then((response) => {
  // print out each calendar's name and description
  response.objects.forEach((resource) => {
    const { data } = resource;
    console.log(`${data.name}: ${data.description}`);
  });
});
Resource Pagination

You may use the getNextPage() method to get the next page without having to supply a page value:

let resourceList;

// Get the first 5 events from primary calendar
account.get({
  url: 'cal/calendars/primary/events',
  params: {
    page_size: 5
  }
}).then((nextResourceList) => {
  resourceList = nextResourceList;
  displayPage();
})


function displayPage() {
  resourceList.objects.forEach((resource) => {
    console.log(
      `${resource.data.name}: ${resource.data.start}~${resource.data.end}`
    );
  });
}

// retrieve next page and display it when user clicks "Next Page"
function onUserClickNextPage() {
  resourceList.getNextPage().then((nextResourceList) => {
    resourceList = nextResourceList;
    displayPage();
  })
}
Iterate through all resources

If you'd like to iterate through all resources without handling pagination by yourself, you can use the iterate() helper method. This method will take a callback function, and it is called multiple times with each resource as the first argument.

You can also specify maxResources if you don't want to iterate through all resources in one shot.

The promise is resolved with a boolean return value. If there are more resources ahead, you can call iterate() again to continue iterating through following resources, or call resetIterateIndex to go back to the very first resource and start over.

let events;

account.get({
  url: 'cal/calendars/primary/events',
  params: { page_size: 10 }
}).then((resourceList) => {
  events = resourceList;
})

function displayEvent(event) {
  // print out event's name and time
  console.log(
    `[${event.data.id}] ${event.data.name}:`
    + `${event.data.start} ~ ${event.data.end}`
  );
}

// show the next 5 calendar events when user clicks "show more"
function onUserClickShowMore() {
  events.iterate({
    callback: displayEvent,
    maxResources: 5
  }).then((hasRemainResources) => {
    if (hasRemainResources) {
      console.log('click "show more" for more events');
    } else{
      console.log('no more events for this calendar');
    }
  });
}

// reset the iteration when the user clicks "clean all"
// when user clicks "show more" next time, the first 5 events will be
// displayed again
function onUserClickCleanAll() {
  events.resetIterateIndex();
}

Download Binary Data

Certain endpoints like thumbnails or file content will return binaries. The responses will contain Binary objects. The data property will be different depending on app's environment:

  • In Browsers, it will be contain binary contents. It is not recommended to access this data property directly. Instead, you could use download method to prompt a download dialog:

    account.get({
      url: `storage/files/${FILE_ID}/contents/`,
    }).then((binary) => {
      // prompt download dialog in browser
      binary.download(FILENAME);
    });
  • In Node.js, it is a readable Stream Object passed from axios

    const express = require('express');
    const kloudless = require('@kloudless/kloudless');
    
    const app = express();
    
    // assume Account Object is already created
    
    app.get('/download/binary', (req, res) => {
      account.get({
        url: `storage/files/${FILE_ID}/contents/`,
      }).then((binary) => {
        // prompt download dialog in browser
        res.setHeader('content-type', binary.headers['content-type']);
        res.setHeader('content-disposition', `attachment; filename=${FILENAME}`);
        binary.data.pipe(res);
      });
    });

Upload Binary Data

Certain endpoints require you to send binary data as request data, like when uploading files.

  • The SDK supports file uploads from a browser by sending the File object obtained from the file input of a form. For example, a form with file input id="fileInput" would send the data as request data:

    const fileInput = document.getElementById('fileInput');
    if (fileInput.files.length) {
      const file = fileInput.files[0];
      account.post({
        url: 'storage/files',
        headers: {
          'X-Kloudless-Metadata': {
            parent_id: 'root',
            name: file.name
          }
        },
        params: {
          overwrite: true
        },
        data: file
      }).then((resource) => {
        console.log('File uploaded: ', resource.data);
      });
    }
  • The SDK also supports file uploads from Node.js by reading the file into a stream object and then sending the stream object as request data:

    const fs = require('fs');
    const path = require('path');
    const filename = 'foo.png';
    const stream = fs.createReadStream(path.resolve(__dirname, filename));
    account.post({
      url: 'storage/files',
      headers: {
        'X-Kloudless-Metadata': {
          parent_id: 'root',
          name: filename
        }
      },
      params: {
        overwrite: true
      },
      data: stream
    }).then((resource) => {
      console.log('File uploaded: ', resource.data);
    });

Check Request Options for other supported data types you can use in options.data to upload files.

Generic Response

For responses that cannot be classified as any of the object types listed above, a general Response Object is created. The Response class is also the parent class of all response types mentioned above.

// calendar API: find availability
account.post({
  url: 'cal/availability',
  data: {
    calendars: ['primary'],
    meeting_duration: 'PT1H',
    constraints: {
      time_windows: [{
          start: '2017-05-20T08:00:00+07:00',
          end: '2017-05-20T12:00:00+07:00'
      },{
          start: '2017-05-21T08:00:00+07:00',
          end: '2017-05-21T12:00:00+07:00'
      }]
    }
  }
}).then((response) => {
  /** response.data would look like:
  {
    "api": "calendar",
    "type": "availability",
    "time_windows": [
      {
        "start": "2017-05-20T01:00:00Z",
        "end": "2017-05-20T05:00:00Z"
      },
      {
        "start": "2017-05-21T01:00:00Z",
        "end": "2017-05-21T05:00:00Z"
      }
    ]
  }
  */
  // print available times
  response.data.time_windows.forEach((time) => {
    console.log(`${time.start} ~ ${time.end}`);
  })

});

Making Requests from Response Objects

The Response objects can also be used to make further requests by using get(), post(), put(), patch(), and delete(). You only need to append URL segments that follow previous request url.

// get the default calendar under this account
account.get({
  url: 'cal/calendars/primary'
}).then((resource) => {
  // print calendar name
  console.log(`Events on calendar ${resource.data.name}`);
  // get the first 5 events of this calendar
  return resource.get({
    // full URL is cal/calendars/primary/events
    url: 'events',
    params: { page_size: 5 }
  })
}).then((resourceList) => {
  resourceList.objects.forEach((resource) => {
    // print out event's name and time
    console.log(
      `${resource.data.name}: ${resource.data.start} ~ ${resource.data.end}`
    );
  })
});

Refer to the documentation on how the url is applied when making further requests from response objects.

Handling Request Failures

Use catch to catch any error when making API requests, the error will be wrapped into Exception Object, and you can get detailed information via statesCode, errorCode, and message field

// assume you make a request to team API with a non-admin account
account.get({
  url: 'team/groups'
}).then((response) => {
  // ...
}).catch((exception) => {
  // if API returns a error
  if (exception.response) {
    // print 'Error: [forbidden] Only administrators can perform this action'
    console.error(`Error: [${exception.errorCode}] ${exception.message}`);
  } else {
    // if no response returned or no requests were made due to invalid options
    console.error(`Error: ${exception.message}`);
  }
});

Other Usages

Refreshing Response Objects

To retrieve the latest data of the response object, simply call refresh().

const fileId = SAMPLE_FILE_ID;

function displayFileInfo(resource) {
  // ...
}

// get this file's metadata
account.get({
  url: `storage/files/${fileId}/`
}).then((resource) => {
  // we want to retrieve latest metadata every 30 seconds in case file is updated
  // via user outside this app.
  displayFileInfo(resource);
  setTimeout(() => {
    resource.refresh();
    // display latest information
    displayFileInfo(resource);
  }, 30000);
});

Calling Upstream Service APIs

You can make requests to upstream service API (a.k.a. pass-through requests) by using raw, in case certain service specific usages are not covered by Kloudless API.

Note that the response will be a response object from axios, there is no object created by SDK.

account.raw({
  url: 'https://api.dropboxapi.com/2/files/list_folder',
  method: 'POST',
}).then((response) => {
  const { data } = response;
  data.entries.forEach((folder) => {
    console.log(folder.name);
  })
});

Keep Returned Bearer Tokens

You should keep the returned Bearer Token via OAuth flow saved somewhere safe (in database, for example), for reuse. This will avoid you asking the user to authorize their account repeatedly.

In browsers, the connectAccount method is resolved with a Account object, you can retrieve the token via the token property.

kloudless.connectAccount({
    scope: 'any.storage',
    appId: YOUR_APP_ID,
  }).then((account) => {
    console.log('Bearer Token: ', account.token);
  });

Verifying Account Bearer Tokens

It is recommended that you verify the Bearer Token before you use it, if the token was not originally retrieved from Kloudless API, .

const token = BEARER_TOKEN; // assume you have retrieved a saved token

kloudless.verifyToken({
  appId: YOUR_APP_ID,
  token,
  initAccount: true // return Account object after verification
}).then((result) => {
  if (result.valid) {
    const  { account } = result;
    console.log('Account email:', account.data.account);
  } else {
    console.error('Token is not valid:', result.error);
  }
}).catch((exception) => {
  // if other API request errors happened
  console.log(`Error initializing account: ${exception.message}`);
});

Making Arbitrary API Requests

The SDK Object can make API requests with get(), put(), post(), patch(), and delete() as well. You will need to type full API Url and supply Authorization header on your own if required.

// list all supported storage services
kloudless.get({
  url: 'public/services',
  params: {
    apis: 'storage'
  }
}).then((response) => {
  response.data.objects.forEach((service) => {
    console.log(service.friendly_name);
  });
});

// get detail of the account associated to the bearer token
kloudless.get({
  url: 'accounts/me',
  headers: {
    Authorization: `Bearer ${TOKEN}`
  }
}).then((response) => {
  const { data } = response;
  console.log(`${data.service_name}: ${data.account}`);
});

Setting the API Version

By default this SDK uses v1 of the Kloudless API. You can use kloudless.setApiVersion() method to change the API Version.

// change API Version to v2
kloudless.setApiVersion(2);

Development

Run npm install to install dev dependencies for development, build and test.

  • Web
BASE_URL=<base_url> KLOUDLESS_APP_ID=<app_id> npm run dev_web
  • Node.js
BASE_URL=<base_url> KLOUDLESS_APP_ID=<app_id> KLOUDLESS_API_KEY=<api_key> npm run dev_node

Note: Currently no hot reloading is provided for Nodejs development environment.

Build

npm run build

Test

npm run test

Support

Feel free to contact us at [email protected] with any feedback or questions.