diff --git a/README.md b/README.md index f45d780..1b20bdd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -A set of functions to simplify building Salable applications in -JavaScript/Node.js. +A set of functions to simplify building Salable applications on the client. ## Installation @@ -7,17 +6,16 @@ JavaScript/Node.js. # npm npm install @salable/js +# pnpm +pnpm add @salable/js + # yarn yarn add @salable/js - -# pnpm -pnpm install @salable/js ``` ## Functions -The library features exports for both ECMAScript Modules (ESM) and CommonJS -(CJS). +The library exports both ECMAScript Modules (ESM) and CommonJS (CJS) builds. ```js import salableJs from '@salable/js' @@ -25,32 +23,29 @@ import salableJs from '@salable/js' const salableJs = require('@salable/js') ``` -For convenience, the functions documented are also added to the `window` object -on the web under the `salable` object. So, `getGrantee` can be accessed via -`window.salable.getGrantee`. +For convenience, the functions documented are also added to the browser's +`window` object as part of the `salable` object. So, `getGrantee` can be +accessed via `window.salable.getGrantee`. ### `getGrantee({ apiKey: string, productUuid: string, granteeId?: string })` -Returns a list of capabilities for the current user. This function is scoped to -a grantee (through the provided `granteeId`) and a product (through the provided -`productUuid`). +Returns the features the provided grantee has access to. -Also returns a `hasCapability` utility function that simplifies the checking of -the provided user's capabilities. +Also returns a `hasFeature` utility function that simplifies feature checking. #### Example ```js import { getGrantee } from '@salable/js' -const { hasCapability, licenses, capabilities, isTest } = await getGrantee({ +const { features, hasFeature } = await getGrantee({ apiKey: 'your-api-key', productUuid: 'your-product-uuid', granteeId: 'your-users-grantee-id', }) -if (hasCapability('edit')) { - console.log('You have the edit capability!') +if (hasFeature('edit')) { + console.log('Grantee has access to the edit feature!') } ``` @@ -75,7 +70,7 @@ const { name, plans } = await getProduct({ }) ``` -### `getCheckoutLink({ apiKey: string, planUuid: string, successUrl: string, cancelUrl: string, granteeId: string, member: string })` +### `getCheckoutLink({ apiKey: string, planUuid: string, successUrl: string, cancelUrl: string, granteeId: string, owner: string })` Returns a checkout link for the specified `planUuid`. @@ -90,7 +85,7 @@ const checkoutLink = await getCheckoutLink({ successUrl: 'https://your.apps/success', cancelUrl: 'https://your.apps/cancel', granteeId: 'your-users-id', - member: 'your-users-id', + owner: 'your-users-id', checkoutEmail: '', // optional, pre-fills email field in Stripe checkout quantity: 5, // optional, the number of seats purchased on checkout (if using per-seat plan, default is minimum number set on plan) currency: 'EUR', // optional, defaults to the product's default currency in Salable diff --git a/lib/__tests__/getCheckoutLink.test.ts b/lib/__tests__/getCheckoutLink.test.ts index 0d3fec2..08e89dc 100644 --- a/lib/__tests__/getCheckoutLink.test.ts +++ b/lib/__tests__/getCheckoutLink.test.ts @@ -13,7 +13,7 @@ describe('getCheckoutLink', () => { cancelUrl: 'https://www.example.com', successUrl: 'https://www.example.com', granteeId: 'test-user-1', - member: 'test-owner-1', + owner: 'test-owner-1', }) expect(checkoutUrl).toEqual('https://stripe.com/') diff --git a/lib/__tests__/getGrantee.test.ts b/lib/__tests__/getGrantee.test.ts index c97ef15..3d0782e 100644 --- a/lib/__tests__/getGrantee.test.ts +++ b/lib/__tests__/getGrantee.test.ts @@ -9,7 +9,7 @@ const baseGetUserValues = { } describe('getGrantee', () => { - describe('without license data', () => { + describe('without subscription data', () => { beforeEach(() => { fetchMock.mockOnce(null, { status: 204, statusText: 'No Content' }) }) @@ -19,7 +19,7 @@ describe('getGrantee', () => { }) }) - describe('with license data', () => { + describe('with subscription data', () => { beforeEach(() => { fetchMock.mockOnce(JSON.stringify(mockResponseData)) }) @@ -28,45 +28,45 @@ describe('getGrantee', () => { expect(getGrantee(baseGetUserValues)).toBeTypeOf('function') }) - it('returns the correct capabilities', async () => { - const { capabilities } = await getGrantee({ + it('returns the correct features', async () => { + const { features } = await getGrantee({ ...baseGetUserValues, granteeId: 'hi', }) - expect(capabilities).toMatchObject(['create', 'read', 'update', 'delete']) - expect(capabilities).toHaveLength(4) + expect(features).toMatchObject(['create', 'read', 'update', 'delete']) + expect(features).toHaveLength(4) }) - describe('hasCapability', () => { - it('correctly checks for individual capabilities', async () => { - const { hasCapability } = await getGrantee({ + describe('hasFeature', () => { + it('correctly checks for individual features', async () => { + const { hasFeature } = await getGrantee({ ...baseGetUserValues, granteeId: 'test-user-1', }) - expect(hasCapability('Edit')).toEqual(false) - expect(hasCapability('Create')).toEqual(true) + expect(hasFeature('Edit')).toEqual(false) + expect(hasFeature('Create')).toEqual(true) }) - it('treats capability names as case-insensitive', async () => { - const { hasCapability } = await getGrantee({ + it('treats feature names as case-insensitive', async () => { + const { hasFeature } = await getGrantee({ ...baseGetUserValues, granteeId: 'test-user-1', }) - expect(hasCapability('Create')).toEqual(true) - expect(hasCapability('create')).toEqual(true) - expect(hasCapability('CReaTE')).toEqual(true) + expect(hasFeature('Create')).toEqual(true) + expect(hasFeature('create')).toEqual(true) + expect(hasFeature('CReaTE')).toEqual(true) }) - it('correctly checks multiple capabilities', async () => { - const { hasCapability } = await getGrantee({ + it('correctly checks multiple features', async () => { + const { hasFeature } = await getGrantee({ ...baseGetUserValues, granteeId: 'test-user-1', }) - expect(hasCapability(['edit', 'create'])).toMatchObject({ + expect(hasFeature(['edit', 'create'])).toMatchObject({ edit: false, create: true, }) diff --git a/lib/getCheckoutLink.ts b/lib/getCheckoutLink.ts index 158a280..93c1f8a 100644 --- a/lib/getCheckoutLink.ts +++ b/lib/getCheckoutLink.ts @@ -4,7 +4,7 @@ export type GetCheckoutLinkArgs = { successUrl: string cancelUrl: string granteeId: string - member: string + owner: string checkoutEmail?: string quantity?: number currency?: 'EUR' | 'USD' | 'GBP' @@ -16,28 +16,23 @@ export async function getCheckoutLink({ successUrl, cancelUrl, granteeId, - member, + owner, checkoutEmail, quantity, currency, }: GetCheckoutLinkArgs) { - const rawSearchParams = { + const searchParams = Object.entries({ successUrl, cancelUrl, granteeId, - member, + owner, customerEmail: checkoutEmail, quantity: quantity?.toString(), currency, - } - - const searchParams = Object.entries(rawSearchParams).reduce( - (acc, [key, value]) => { - if (!value) return acc - return { ...acc, [key]: value } - }, - {}, - ) + }).reduce((acc, [key, value]) => { + if (!value) return acc + return { ...acc, [key]: value } + }, {}) const url = `https://api.salable.app/plans/${planUuid}/checkoutlink?` + diff --git a/lib/getGrantee.ts b/lib/getGrantee.ts index 1ebf2a4..67de358 100644 --- a/lib/getGrantee.ts +++ b/lib/getGrantee.ts @@ -4,38 +4,32 @@ type GetGranteeParams = { granteeId: string } -type HasCapability = { - (capability: string): boolean - (capabilities: readonly T[]): { [Key in T]: boolean } +type HasFeature = { + (feature: string): boolean + (features: readonly T[]): { [Key in T]: boolean } } export type UserData = { /** - * A list of combined capabilities that the user has access to based on their - * active licenses. This array contains both active and in-active - * capabilities. + * A list of features that the grantee has access to based on their + * active subscriptions. */ - capabilities: string[] + features: string[] /** - * Used to check whether a user has either an active capability, or a list of - * supplied capabilities. Capability names are case insensitive. + * Used to check whether a grantee has an active feature or number of + * features. Feature names are case insensitive. * - * All inactive capabilities will return false. This includes capabilities - * that are on 'canceled' licenses. If you want to check for a capability - * that isn't active, or is on a canceled license, use the `capabilities` - * array returned by this hook. - * - * To check a single capability: + * To check a single feature: * ``` - * const hasCreate = hasCapability('create'); + * const hasCreate = hasFeature('create'); * ``` * - * To check a list of capabilities: + * To check a list of features: * ``` - * const { create, update } = hasCapability(['create', 'update']); + * const { create, update } = hasFeature(['create', 'update']); * ``` */ - hasCapability: HasCapability + hasFeature: HasFeature } async function _getGrantee({ @@ -55,42 +49,40 @@ async function _getGrantee({ if (!response.status.toString().startsWith('2')) throw new Error('Could not fetch user licenses...') - let capabilities: string[] = [] + let features: string[] = [] try { - capabilities = (await response.json()).capabilities + features = (await response.json()).capabilities } catch (error) {} // Overload for checking an individual capability. - function hasCapability(capability: string): boolean + function hasFeature(feature: string): boolean // Overload for checking a list of capabilities and receiving an object with // those capabilities as keys. - function hasCapability( - capabilities: readonly T[], + function hasFeature( + features: readonly T[], ): { [Key in T]: boolean } // Implementation of the hasCapability function based on the above overloads. - function hasCapability( - capabilityOrCapabilities: string | readonly string[], + function hasFeature( + featureOrFeatures: string | readonly string[], ): boolean | { [Key in T]: boolean } { - const activeCapabilities = capabilities.map((capability) => - capability.toLowerCase(), - ) + const lowerCasedFeatures = features.map((f) => f.toLowerCase()) - if (typeof capabilityOrCapabilities === 'string') { - return activeCapabilities.includes(capabilityOrCapabilities.toLowerCase()) + if (typeof featureOrFeatures === 'string') { + return lowerCasedFeatures.includes(featureOrFeatures.toLowerCase()) } - return capabilityOrCapabilities.reduce((acc, curr) => { + return featureOrFeatures.reduce((acc, curr) => { return { ...acc, - [curr]: activeCapabilities.includes(curr.toLowerCase()), + [curr]: lowerCasedFeatures.includes(curr.toLowerCase()), } }, {}) as { [Key in T]: boolean } } return { - capabilities, - hasCapability, + features, + hasFeature, } } diff --git a/package.json b/package.json index 7829cc2..5d41436 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@salable/js", "version": "4.1.0", - "description": "A set of functions to simplify building Salable applications with JS", + "description": "A set of functions to simplify building Salable applications on the client.", "main": "./dist/index.cjs", "module": "./dist/index.mjs", "types": "./dist/index.d.ts",