npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@pluginlab/node-sdk

v0.6.0

Published

A package to manage PluginLab authentication and more for your users.

Downloads

89

Readme

PluginLab Admin Node SDK

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

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: '[email protected]',
//         signInMethod: 'google'
//     },
//     createdAtMs: 1685804986789,
//     customFields: {},
//     givenName: 'Kevin',
//     pictureUrl: 'https://somepicture.com',
//     familyName: 'Piacentini',
//     id: 'mem_39a23f618be79391a31387d2c3d61967e83f7010',
//     email: '[email protected]',
//     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.

List members

You can list your members using the getMembers method.

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

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.
    }
}

Create a member

const member = await auth.createMember({
    email: '[email protected]',
    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

TheverifySignatureOrThrow method will throw aPluginLabWebhookError if the signature is invalid.

ThePluginLabWebhookError 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;

Monetization

This SDK allows you to access some information related to user monetization.

Get user's current quota usage

If you are using quotas to rate limit and paywall your plugin then you can get the current quota of a given user as shown below:

const monetization = app.getMonetization()
const usage = await monetization.getMemberQuotaUsage('mem_6b8f55542d6b8668f95efda684179bf6555e3624');

The returned usage is an object that fits the following type:

interface MemberQuotaUsage {
	interval: string;
	isOverLimit: boolean;
	isUnlimited: boolean;
	resetInDays: number;
	usageCount: number;
	usageLimit: number;
}

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.