Articles on: Developers

NodeJS SDK

NodeJS SDK



Altough it's possible to rebuild your own SDK by using our PluginLab RESTful API, if you're using NodeJS as a backend technology it will be much simpler to use our NodeJS SDK.

To use our NodeJS SDK, follow the instuctions below or directly go to our NPM Repository.

Installation



npm i -S @pluginlab/node-sdk



Getting started




To get started you have to init the PluginLabApp on your backend.

You will need your pluginId and the secret you can generate from your PluginLab dashboard.

import { PluginLabApp } from "@pluginlab/node-sdk";

const app = new PluginLabApp({
    pluginId: '<THE_PLUGIN_ID>',
    secretKey: '<THE_SECRET>',
});

// The PluginLabAuth is the main entry point for all the members related operations
const auth = await app.getAuth();



Verify the user token



Everytime ChatGPT will talk to your backend through PluginLab proxy, it will send the user token in the Authorization header.

You can verify this token using the verifyIdToken method.

Note: verifyIdToken returns the entire payload since version v0.2.0. Before v0.2.0 only the uid was returned.

// If the token is valid, it will return the user id
const payload = await auth.verifyIdToken('TOKEN_FROM_HEADER')

try {
    // If it's invalid it will throw an error
    const wrong = await auth.verifyIdToken('wrong');
} catch (e) {
    console.error('Error', e);
}



The returned payload matches the PluginLabTokenPayload interface.

export interface TokenPayloadUser {
    id: string;
    email: string;
    name: string | null;
    givenName: string | null;
    planId: string | null;
    priceId: string | null;
}

export interface PluginLabTokenPayload {
    uid: string;
    aud: string;
    iss: string;
    iat: number;
    exp: number;
    user: TokenPayloadUser;
}


Get a member by id or email



Note that the payload sent in the verification token (see above) contains almost all user info you should need for production use. So prefer extracting user informations from the idToken instead of calling the API to fetch the member at every request coming from PluginLab. This would result in a meaningless latency in your backend. :)

const memberById = await auth.getMemberById(member.id);
console.log('Member by id', memberById);

const memberByEmail = await auth.getMemberByEmail(member.auth.email);
console.log('Member by email', memberByEmail);

// Member example:
// {
//     metadata: { updated: 'metadata' },
//     auth: {
//         isVerified: true,
//         hasPassword: false,
//         providerUserId: '104060273339127949239',
//         email: 'kevin@pluginlab.ai',
//         signInMethod: 'google'
//     },
//     createdAtMs: 1685804986789,
//     customFields: {},
//     givenName: 'Kevin',
//     pictureUrl: 'https://somepicture.com',
//     familyName: 'Piacentini',
//     id: 'mem_39a23f618be79391a31387d2c3d61967e83f7010',
//     email: 'kevin@thearenaproject.co',
//     name: 'Kevin Piacentini',
//     updatedAtMs: 1685827381733
// }


Note: some fields such as givenName, name, familyName, pictureUrl are available only if authenticated with an OAuth provider that provides these info, such as Google.

Get member's identities



When a member logs in using a third-party auth provider, the latter generates an access token and optionally a refresh token that the application can use
to interact with the third-party service on behalf of the user.

PluginLab allows you to get the identities that have been generated when the user logged in:

const identitites = await auth.getMemberIdentities('mem_fd9a1ba5e385e3d97412cdfbd7b8284c4c038e18')

if (identities.google) {
    console.log(`User has a google identity attached! Access token is: ${google.accessToken}`)
}

if (identities.github) {
    //
}

// applies to each third-party provider supported by PluginLab - see typings for more info


In this example identities will be a type of Record<IdentityProvider>, ProviderIdentityData>.

export type IdentityProvider = 'google' | 'gitlab' | 'microsoft' | 'github';

export interface IdentityProviderData {
	provider: IdentityProvider;
	providerUserId: string;
	lastUsedAtMs: number | null;
	accessTokenExpiresAtMs: number | null;
	accessToken: string | null;
	refreshTokenExpiresAtMs: number | null;
	refreshToken: string | null;
}


Refresh identity access token



For OAuth providers such as Google or Gitlab the access token has a limited lifetime. PluginLab provides an endpoint in case your token needs to be refreshed.

The following endpoint will refresh the token

const refreshedIdentity = await auth.refreshIdentityToken('<MEMBER_ID>', 'google');

console.log('Fresh access token', refreshedIdentity.accessToken);


The provider id can be either google or gitlab at this moment.

This endpoint will return a Promise<IdentityProviderData>.

Note this endpoint currently works only with google and gitlab. Github does not provide any refresh token at this moment. If you need support for more providers feel free to reach out.


Handling refresh errors



Sometimes it's possible that the refresh token is revoked or expired. In that case PluginLab will not be able to refresh the token anymore.
In that situation, PluginLab will return the following error with a HTTP CODE 422:

try {
    const freshIdentity = await auth.refreshMemberIdentityToken('<MEMBER_ID>', 'gitlab');
} catch (e) {
    if (e?.errorInfo?.code === 'auth/refresh-token-failed') {
        // then return redirect your user to the auth-portal page.
    }
}



List members



You can list your members using the getMembers method.

const numberPerPage = 10;
const { items, total, nextPageToken } = await auth.getMembers(numberPerPage, null);


Create a member



const member = await auth.createMember({
    email: 'kevin@pluginlab.ai',
    password: 'some-strong-password',
    isVerified: true,
    metadata: {
        optional: 'any metadata',
        something: 'else',
    }
});

console.log('Member created', member);


Update a member



// The update acts as a patch.
// So every field is optional.
// Although metadata will be replaced as a whole object if you specify it.
await auth.updateMember(member.id, {
    metadata: {
        updated: 'metadata',
    },
    name: 'Kevin',
    isVerified: false,
    givenName: 'Kevin',
    familyName: 'Durand',
    pictureUrl: 'https://some-picture.com',
});


Delete a member



await auth.deleteMember(member.id);
console.log('Member deleted');



Webhooks



The webhook utility allows you to verify the signature of a webhook request sent by PluginLab.
This is useful to make sure the request is coming from PluginLab and not from a third party.


Basic usage example




To verify the signature you only have to create a new instance of the Webhook class with your secret key.
Then you can use either the verifySignatureOrThrow or isSignatureValid methods to verify the signature (depending on the coding style you prefer).

These methods take the body (as a string or as an object) and the signature.

Example in an express server:

import { Webhook, WebhookHeaders, PluginLabWebhookError } from "@pluginlab/node-sdk";

app.post('/webhook-example-1', (req, res) => {
    try {
        const signature = req.header(WebhookHeaders.Signature);

        const webhook = new Webhook('YOUR_WEBHOOK_SECRET');
        webhook.verifySignatureOrThrow(req.body, signature)
        
        // Here we know the request has been sent by PluginLab

        return res.status(200).json({ message: 'Webhook OK' });
    } catch (e) {
        if (e instanceof PluginLabWebhookError) {
            return res.status(400).json({ code: e.code, message: e.message });
        }
        return res.status(500).json({ message: 'Internal server error' });
    }
})

app.post('/webhook-example-2', (req, res) => {
    const hookSignature = req.header(WebhookHeaders.Signature);

    const webhook = new Webhook('YOUR_WEBHOOK_SECRET');
    const isValid = webhook.isSignatureValid(req.body, hookSignature)

    if (!isValid) {
        return res.status(400).json({ message: 'Invalid signature' });
    }

    // Here we know the request has been sent by PluginLab
    
    return res.status(200).json({ message: 'Webhook OK' });
})


Webhook headers



PluginLab sends many headers with the webhook request. The WebhookHeaders object contains all the headers names.

export enum WebhookHeaders {
    Signature = 'X-PluginLab-Signature',
    Event = 'X-PluginLab-Event',
    DeliveryId = 'X-PluginLab-Delivery-Id',
    HookId = 'X-PluginLab-Hook-Id',
    PluginId = 'X-PluginLab-Plugin-Id',
    SignatureVersion = 'X-PluginLab-Signature-Version',
    SignatureAlgorithm = 'X-PluginLab-Signature-Algorithm',
    PoweredBy = 'X-Powered-By',
    Timestamp = 'X-PluginLab-Timestamp',
}


usage example:

import {WebhookHeaders } from '@pluginlab/node-sdk';

const signature = req.header(WebhookHeaders.Signature);



Webhook errors



The`verifySignatureOrThrow` method will throw a`PluginLabWebhookError` if the signature is invalid.

The`PluginLabWebhookError` has a code and a message that can be the following:

export const MissingSecretError = {
    code: 'webhook/missing-secret',
    message: 'Missing secret. Please init the Webhook with a secret.',
} as const;

export const MissingBodyError = {
    code: 'webhook/missing-body',
    message: 'Missing body. Did this event originate from a PluginLab webhook?',
} as const;

export const MissingSignatureError = {
    code: 'webhook/missing-signature',
    message:
        'Missing hook signature. Did this event originate from a PluginLab webhook?',
} as const;

export const InvalidBodyTypeError = {
    code: 'webhook/invalid-body-type',
    message:
        'Invalid body type. The body must be either an object or a string.',
} as const;

export const InvalidSignatureError = {
    code: 'webhook/invalid-signature',
    message: 'Invalid hook signature.',
} as const;



Events



On PluginLab, an event is used as a representaiton of a plugin request and is traditionally generated on each request made by ChatGPT.
The SDK offers limited but useful utilities to deal with events.

To get started, you need to leverage the event service by calling the app.getEvent method.

// assuming app was previously initialized as showed above
const event = app.getEvent();


Create a custom event



If you want to create custom events that will appear in your PluginLab dashboard and count as part of your users' quota, you can rely on the createCustom method.


This is especially useful if your service is accessible from other means that ChatGPT but you still want to account for usage using PluginLab.


event.createCustom({
  // case insensitive, 10 chars. max
  eventSource: 'WEB_UI', 
  // location is optional
  location: {
    countryCode: 'US',
    subdivisionCode: 'CA', // optional
  },
  // optional, isInQuota is ignored if this is not set
  memberId: 'mem_fd9a1ba5e385e3d97412cdfbd7b8284c4c038e18',
  // optional, defaults to true and requires member to be set
  isInQuota: true
})


Assuming the provided memberId refers to a valid plugin member then a new event will get created once the above code runs. It will appear in your PluginLab dashboard's event section.

Updated on: 17/08/2023

Was this article helpful?

Share your feedback

Cancel

Thank you!