šŸš€ Big News:Socket Has Acquired Secure Annex.Learn More →
Socket
Book a DemoSign in
Socket

knack-api-helper

Package Overview
Dependencies
Maintainers
1
Versions
57
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

knack-api-helper

Methods for interacting with the Knack API using both view-based and object-based authentication

Source
npmnpm
Version
2.2.3
Version published
Weekly downloads
18
12.5%
Maintainers
1
Weekly downloads
Ā 
Created
Source

KnackAPI

Methods to help work with the Knack API.

Looking for the Changelog?

Installation

NodeJS

  • Install the package from npm. In a terminal run npm install knack-api-helper --save
  • Import the package into your code.
//example nodejs file
const KnackAPI = require('knack-api-helper');

//Initialise the library from KnackAPI variable
const knackAPI = new KnackAPI({
    applicationId: 'YOUR-APPLICATION-ID',
    auth: 'view-based'
});

//Use knackAPI as needed from here.

Browser (including Knack Javascript area)

A javascript file bundled with all needed dependencies for use in the browser is available via CDNJS (note only available from version 1.0.0 and higher)

https://cdn.jsdelivr.net/npm/knack-api-helper@X.X.X/browser.js

Build the URL you want to use, replacing X.X.X with the version number. An example of a finished URL is:

https://cdn.jsdelivr.net/npm/knack-api-helper@2.1.5/browser.js

Loading into a HTML page

  • Add the browser.js file as a script tag in the head of your HTML page
<head>
    <script src="https://cdn.jsdelivr.net/npm/knack-api-helper@X.X.X/browser.js"></script>
</head>
  • The KnackAPI object is now stored in the window object. So, you can add another script tag somewhere in the body of your HTML page, to do what you need to do.
<body>
    <script>
        //Initialise the library from KnackAPI variable, and store it in the window object
        window.knackAPI = new KnackAPI({
            applicationId: 'YOUR-APPLICATION-ID',
            auth: 'view-based'
        });

        //Use knackAPI as needed from here.
    </script>

Loading into Knack javascript area

  • Import browser.js into the Knack javascript area using Javascript. Using KnackInitAsync prevents the app loading until the external browser.js script has loaded
//KnackInitAsync blocks the app loading until callback() is run 
KnackInitAsync = function($, callback) {

    // REQUIRED: Explicitly include jQuery
    window.$ = $;

    const scripts = [
        {src: 'https://cdn.jsdelivr.net/npm/knack-api-helper@X.X.X/browser.js'}
    ]

    loadScripts(
        scripts, 
        callback,
        () => {console.log('error loading scripts')}
    );
    
}

//Helper function to load scripts into a Knack app
const loadScripts = (scripts, onSuccess, onFailure) => {
    let loadedScripts = 0;
    let failedScripts = 0;

    if(typeof onSuccess !== 'function'){
        onSuccess = function(){
            console.log('Scripts loaded');
        }
    }

    if(typeof onFailure !== 'function'){
        onFailure = function(){
            console.error('Failed to load scripts');
        }
    }

    scripts.forEach(({ src, type }) => {
        const script = document.createElement('script');
        script.src = src;
        if (type) {
            script.type = type;
        }

        script.addEventListener('load', () => {
            loadedScripts++;
            if (loadedScripts === scripts.length) {
                onSuccess();
            }
        });

        script.addEventListener('error', () => {
            failedScripts++;
            onFailure();
        });

        document.body.appendChild(script);
    });
};

Initialization

Once you have imported knack-api-helper as KnackAPI via one of the above methods, you're ready to use it.

The first step is to initialize a new instance from the KnackAPI class. This can be done in one of two ways, depending what sort of authentication you want to use.

Object-based authentication

//Server-side javascript code (eg Node.js)
const knackAPI = new KnackAPI({
    auth: 'object-based',
    applicationId: 'YOUR-APPLICATION-ID',
    apiKey: 'YOUR-API-KEY'
});

Warning! Do not use object-based authentication in client-side code (including the Knack javascript area), because it exposes your api key.

View-based authentication

Public (non-authenticated) user

The basic way to initialize with view-based authentication is as follows. This will operate with the permissions of public users, rather than logged in users, so only API calls to views on public scenes will work.

This can be safely used in any client-side or server-side code, including the Knack javascript area.

//Client or server-side javascript code
const knackAPI = new KnackAPI({
    auth: 'view-based',
    applicationId: 'YOUR-APPLICATION-ID'
});

Authenticated users

If you want to make view-based API calls to login-protected pages, you'll need to provide a user token to Knack-Api-Helper. How you do this depends where you're running your code.

In the Knack javascript area

If you're running your code in the Knack builder javascript area, you can use the built-in Knack method window.Knack.getUserToken() to obtain a user token for the currently logged in user. Therefore, you could initialize knackAPI like so:

//Javascript running in Knack javascript area
const knackAPI = new KnackAPI({
    auth: 'view-based',
    applicationId: 'YOUR-APPLICATION-ID',
    userToken: Knack.getUserToken()
});
Outside of the Knack javascript area

If you want to use view-based authentication outside of the Knack javascript area (but still need to access login-protected pages) you won't have access to window.Knack.getUserToken().

Therefore, you'll need to do a remote login to obtain a user token and append it to the knackAPI instance. See the API reference -> remote login section below for more information.

Warning, do not include a username and password in code that is running client-side, including the Knack javascript area, because it exposes your username and password to the public!

GENERAL BEHAVIOUR

This sections outlines the general behaviour of Knack-Api-Helper and how it handles errors.

Auto-retry and delay between retries

knack-api-helper will auto-retry failed API calls when sensible, but will return an error immediately if the error is permanent.

Http error codeAuto-retry?Delay between retriesDetails
4XX (except 429)NoN/AThese codes are related to invalid authentication so there is no point retrying failed requests.
429YesExponential backoffThis code occurs when you make more than 10 requests per second to the Knack API. Retry is sensible, and exponential backoff is recommended by Knack API documentation, so we do that.
5XXYes1 second (static)These codes are related to temporary server outages or similar. Retrying is sensible, but there's no need to use exponential backoff, so we keep things faster by just waiting 1 second and retrying.

API REFERENCE

Remote login

Remote login is useful when:

  • You want to use view-based authentication to make API calls to login-protected pages (see initializing above)
  • window.Knack.getUserToken() is not available to you because you're running code somewhere outside of the Knack javascript area

Knack provides a remote login method to perform a remote login to obtain a user token to authenticate view-based API calls. Knack-api-helper provides the following methods that make use of this.

Warning! Do not use remote login in any client-side code (including the Knack javascript area), because it xposes your email and password to the public!

.login()

Performs a remote login and appends the returned user token to the knackAPI instance to authenticate future view-based API calls.

The function also returns the obtained user token (JWT string generated by Knack) in case you want to use it elsewhere in your code.

Pass .login() a settings object with these parameters:

ParameterTypeRequired?Details
emailstringYesAn email address for a user in your Knack app
passwordstringYesThe password for the above user

Example:

//Server-side javascript (eg Node.js code)

//Initialize without a user token
const knackAPI = new KnackAPI({
    auth: 'view-based',
    applicationId: 'YOUR-APPLICATION-ID',
});

//Now we remotely login to Knack to obtain a user token & append it to knackAPI
try {
    await knackAPI.login({
        email: 'a_valid_login@email.com',
        password: 'A-VALID-PASSWORD'
    });

    //Now you can run knackAPI methods authenticated as the user you logged in as!

} catch(err){
    console.log(err);
}

.remoteLogin()

This is a standalone method for remote login to a Knack app.

Returns the full request object (data for the Knack user session), rather than just the token.

Does NOT add the user token to your knackAPI object.

Pass .remoteLogin() a settings object with these parameters:

ParameterTypeRequired?Details
emailstringYesAn email address for a user in your Knack app
passwordstringYesThe password for the above user
//Server-side javascript (eg Node.js code)

//Initialize without a user token
const knackAPI = new KnackAPI({
    auth: 'view-based',
    applicationId: 'YOUR-APPLICATION-ID',
});

//Remote login to obtain full session object
try {
    const login = await knackAPI.remoteLogin({
        email: 'a_valid_login@email.com',
        password: 'A-VALID-PASSWORD'
    });
    const session = login.json.session;
    console.log(session);

    //Note that your knackAPI instance will NOT have the user token appended to it

} catch(err){
    console.log(err);
}

Validating a User Token - validateSession()

This method lets you validate that a Knack user token is valid and current for the specified app.

This is useful in a few scenarios:

  • You want to check if a user token expressly provided to knack-api-helper is valid before using it to make view-based API calls (avoiding potential failure of API calls later on)
  • You are using Knack-api-helper in server-side code that is triggered via a http request from a Knack app. You want to ensure that a logged in Knack user initiated the http request before running other logic (eg before running server-side object-based API calls).

Behaviour

  • Checks that there is a valid session for the applicationId that you initialised Knack-api-helper with
  • Session is considered valid if all of these are true:
    • The http request to https://api.knack.com/v1/session/token succeeds
    • A session object is returned
    • The session.status is 'active'
    • The session.user.account_status is 'active' (ie the user is listed as 'active' in the Knack user table, rather than 'inactive' or 'pending approval')
    • The session.user.approval_status is 'approved'
    • If a userRoleCheck parameter is provided, the user is a member of the specified user role (session.user.profileKeys includes the specified profile_key)
  • Return: true (session is valid) or false (session is not valid)

Pass validateSession() a settings object with these parameters:

ParameterTypeRequired?Details
userTokenstringYesA Knack user token string
userRoleCheckstringNoAn optional (single) user role that you want to check membership of eg profile_10. This corresponds to the object_key (eg object_10) of the user role object you want to check membership of

Example usage

//Initialise knack-api-helper
const knackAPI = new KnackAPI({
    auth: "view-based",//Could also initialise as object-based
    applicationId: "YOUR-APPLICATION-ID",
    //Could also initialise with a user token if desired
});

//Check that a user token is valid for the Knack app
const isAuthorized = await knackAPI.validateSession({
    userRoleCheck: 'profile_17',//The role you want to check for membership of (optional)
    userToken: 'SOME_KNACK_USER_TOKEN'
});

console.log(isAuthorized);//expected value: true or false

if(!isAuthorized){
    return;//Stop executing your code
} else {
    //Continue running your code
}

There are 8 types of record-related API calls (ie API calls that deal with records in your Knack database) supported by Knack-api-helper. These are:

  • get
  • getMany
  • post
  • postMany
  • put
  • putMany
  • delete
  • deleteMany

All the record-related API calls share these common parameters.

ParameterTypeRequired?Auth type applies toDetails
viewstringWhen using view-basedView-based onlyThe view to use for making the view-based API call eg view_10
scenestringWhen using view-basedView-based onlyThe scene containing the view used to make the view-based API call eg scene_25
objectstringWhen using object-basedObject-based onlyThe object (database table) we are making the API call to eg object_1
helperDataobjectNoBothAny arbritrary object you want to include with the API call. This will get returned to you when you receive the response to the API call.

Useful for tracking data about the request that wouldn't ordinarily be easy to understand from the data received back from the API call.
retriesintegar >= 0NoBothThe number of times to retry the API call/s on failure, if the error code indicates that a retry might fix the error (see above). Defaults to 5 if no value is provided.

Note there is a current issue: customizing the amount of retries doesn't work for getMany. getMany will always retry 5 times.

Additional parameters are also available for specific API calls. These are documented in the relevant sections below (see API Reference).

get (single)

This gets a single record from the Knack api, based on the specified record ID.

get example

try {
    
    const knackAPI = new KnackAPI({
        auth: 'view-based',
        applicationId: 'YOUR-APPLICATION-ID'
    });

    await knackAPI.login({
        email: 'email@email.com',
        password: 'password'
    });

    const response = await knackAPI.get({
        scene: 'scene_9',
        view: 'view_20',//View_20 is a details, grid, search or other view showing a record
        recordId: '62e1e6b8754ba000219b0d83'
    });

    const record = response.json;
    console.log(record)//Expected output: a javascript object representing the Knack record

} catch(err) {
    console.log(err);
}

post (single)

Create a single record.

post example

try {
    
    const knackAPI = new KnackAPI({
        auth: 'view-based',
        applicationId: 'YOUR-APPLICATION-ID'
    });

    await knackAPI.login({
        email: 'email@email.com',
        password: 'password'
    });

    const response = await knackAPI.post({
        scene: 'scene_9',
        view: 'view_21',//view_21 is a "new record" form that includes field_27
        body: {field_27: 'something'},
    });

    const recordCreated = response.json;
    console.log(recordCreated)//Expected output: a javascript object representing the Knack record created

} catch(err) {
    console.log(err);
}

put (single)

Update a single record, based on the specified record ID.

put example

try {
    
    const knackAPI = new KnackAPI({
        auth: 'view-based',
        applicationId: 'YOUR-APPLICATION-ID'
    });

    await knackAPI.login({
        email: 'email@email.com',
        password: 'password'
    });

    const response = await knackAPI.put({
        scene: 'scene_9',
        view: 'view_22',//view_22 is an "update record" form containing field_27
        recordId: '62e1e6b8754ba000219b0d83',
        body: {field_27: 'something'},
    });

    const recordUpdated = response.json;
    console.log(recordUpdated)//Expected output: a javascript object representing the Knack record updated

} catch(err) {
    console.log(err);
}

delete (single)

Delete a single record, based on the specified record ID.

delete example

try {
    
    const knackAPI = new KnackAPI({
        auth: 'view-based',
        applicationId: 'YOUR-APPLICATION-ID'
    });

    await knackAPI.login({
        email: 'email@email.com',
        password: 'password'
    });

    const deleteResponse = await knackAPI.delete({
        recordId: '62e1e6b8754ba000219b0d83', 
        scene: 'scene_9',
        view: 'view_21',//view_21 is a view with a delete link like a grid or details view
    });

    console.log(deleteResponse)//Expected output: {"delete": true}

} catch(err) {
    console.log(err);
}

getMany

(partially written)

Parameters of getMany request

getMany accepts the Standard parameters for record-related API calls.

  • Note: in the context of getMany (if using view-based authentication) the view parameter should point to a Knack app view that lists many records such as a grid, search or list view.

Additional parameters, on top of the standard ones, for getMany are as follows:

ParameterTypeRequired?Details
filtersFilters objectNoA javascript object version of Knack filter object
formatstringNoKnack API call formatting options (raw, both, or html)
rowsPerPageintegarNo. Defaults to 1000 if not provided.How many records to get per page of data. See pagination Knack docs
startAtPageintegarNo. Defaults to 1 if not providedThe first page of data to get. Knack-api-helper will fetch this page of data, then any subsequent pages in order (page 1,2,3,4 etc) until all records are fetched or until it has fetched the maxRecordsToGet number of records (see below).
E.g., if there are 10000 records available and you set startAtPage: 5 (and leave rowsPerPage and maxRecordsToGet at their default values), knack-api-helper will fetch pages 5-10, so you will receive the last 5000 records of the available 10000.
maxRecordsToGetintegarNo. Defaults to all records if not provided.The maximum number of records to get. E.g., if you set this to 1500, and there are 30,000 records available, only the first 1500 records will be fetched.
Knack-api-helper will fetch pages in order, it will not go "backwards" to earlier pages to fetch additional records. E.g., if a Knack object has only 2000 records and you set startAtPage: 2 and maxRecordsToGet: 1500, the first 1000 records will be skipped and you will only get the final 1000 records (not 1500).

Advantages of using view-based authentication for getMany

There are several advantages of using a view-based getMany request, even when you run it with server-side code:

  • You can get fields from connected objects (if you include them in the underlying view you are making the API call to) alongside the main records. This can avoid the need for extra API calls to join data.
  • You can build your filtering logic in the data-source settings of view, avoiding the need to use javascript to build filters.

Example getMany request (object based authentication)


const knackAPI = new KnackAPI({
    auth: 'object-based',
    applicationId: 'your application ID',
    apiKey: 'your API key'
});

//object_1 has 20,000 records available

const response = await knackAPI.getMany({
    object: 'object_1',
    format: 'raw',
    rowsPerPage: 500,
    startAtPage: 5,//Skip the first 2000 records (ie 500 records per page * 4 pages skipped = 2000)
    maxRecordsToGet: 3000
});

console.log(response.records);
//Expected output: an array of 3000 records from object_1, contianing only the raw field data

Example getMany request (view-based authentication)

try {
    const knackAPI = new KnackAPI({
        auth: 'view-based',
        applicationId: 'your application ID',
    });

    await knackAPI.login({
        email: 'email@email.com',
        password: process.env.password
    });

    const response = await knackAPI.getMany({
        scene: 'scene_10',
        view: 'view_26',//view_26 is a table/grid view or a search view
        format: 'raw',
        //Other settings are available as per previous example, if needed
    });

    console.log(response.records);
    //Expected output: an array of records from view_26, contianing only the raw field data
} catch (err) {
    console.log(err);
}

postMany

Create many records using a single API call. Uses promise.allSettled under the hood, to allow for some requests to succeed and some to fail.

postMany example (view-based authentication)

(Partially written)

Example many request with error handling individual errors

This will auto-retry as per default settings just like a single request


try {

    const knackAPI = new KnackAPI({
        auth: 'view-based',
        applicationId: 'YOUR-APPLICATION-ID'
    });

    await knackAPI.login({
        email: 'email@email.com',
        password: 'password'
    });

    const recordsToCreate = [
        {field_22: 'some data 1'},
        {field_22: 'some data 2'},
        {field_22: 'some data 3'},
        {field_22: 'some data 4'},
        {field_22: 'some data 5'},
        {field_22: 'some data 6'},
    ]

    const responses = await knackAPI.postMany({
        scene: 'scene_106',
        view: 'view_269',
        records: recordsToCreate,
    });

    if(responses.summary.rejected > 0){
        res.summary.errors.forEach(err => {
            errorHandler(err.reason);
        })
    }

} catch(err){
    //To catch errors from knackAPI.login()
    //ERrors from .postMany() will not reach here
    console.log(err)
}

putMany

Update many records using a single API call. Uses promise.allSettled under the hood, to allow for some requests to succeed and some to fail.

putMany example (view-based authentication)

try {
    const knackAPI = new KnackAPI({
        auth: 'view-based',
        applicationId: 'YOUR-APPLICATION-ID'
    });

    await knackAPI.login({
        email: 'email@email.com',
        password: 'password'
    });

    const recordsToUpdate = [
        {id: '62e1e6b8754ba000219b0d83', field_22: 'some data 1'},
        {id: '62e1e6b8754ba00021XXsd83', field_22: 'some data 2'},
        {id: '622341b8754ba000212f0d83', field_22: 'some data 3'},
        {id: '62e1e6123dfaw345b0223d83', field_22: 'some data 4'},
    ]

    const responses = await knackAPI.postMany({
        scene: 'scene_106',
        view: 'view_269',//view_269 is a form view with the appropriate fields editable
        records: recordsToUpdate,
    });

    if(responses.summary.rejected > 0){
        res.summary.errors.forEach(err => {
            errorHandler(err.reason);
        })
    }
} catch(err){
    //To catch errors from knackAPI.login()
    //Errors from .postMany() will not reach here
    console.log(err)
}

deleteMany

deleteMany example

try {
    const knackAPI = new KnackAPI({
        auth: 'view-based',
        applicationId: 'YOUR-APPLICATION-ID'
    });

    await knackAPI.login({
        email: 'email@email.com',
        password: 'password'
    });

    const recordsToDelete = [
        {id: '62e1e6b8754ba000219b0d83'},
        {id: '62e1e6b8754ba00021XXsd83'},
        {id: '622341b8754ba000212f0d83'},
        {id: '62e1e6123dfaw345b0223d83'},
    ]

    const responses = await knackAPI.postMany({
        scene: 'scene_106',
        view: 'view_269',//view_269 is a form view with the appropriate fields editable
        recordsToUpdate,
    });

    return await knackAPI.deleteMany({
        records: recordsToDelete,
        scene: 'scene_9',
        view: 'view_21',
    });

    if(responses.summary.rejected > 0){
        res.summary.errors.forEach(err => {
            errorHandler(err.reason);
        })
    }
} catch(err){
    //To catch errors from knackAPI.login()
    //ERrors from .postMany() will not reach here
    console.log(err)
}

FAQs

Package last updated on 16 Sep 2023

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts