0.0.1-canary.60 • Published 8 days ago

@whop-apps/iframe v0.0.1-canary.60

Weekly downloads
-
License
ISC
Repository
-
Last release
8 days ago

Whop Iframe SDK

Whop apps are embedded into the whop application using iframes. This SDK provides a type-safe way for you to communicate with the whop application using a request/response style API powered by window.postMessage.

Since this package relies on window.postMessage it will only work in the browser, however, it is safe to import this package in your server-side code, as long as you don't rely on using it.

Setup

The main function exported from this package is the createAppIframeSDK function. When called, this function sets up a listener for messages from the main whop application (using window.on('message', ...))

It returns an object containing async functions that allow you to interact/query with the whop application. The object also returns the _cleanupTransport function. This function removes the listener setup initially. This allows you safely to mount the SDK in react components using a useEffect call.

However, most applications will want to mount the SDK once, and then use it throughout the lifetime of the application. In this case you can simply create the SDK once on the module level, and then use it throughout your app like so:

import { createAppIframeSDK } from '@whop-apps/sdk';

export const WhopApp = createAppIframeSDK({
  onMessage: {},
});

React Use-Effect Example

import { createAppIframeSDK } from '@whop-apps/sdk';
import { useEffect, useState } from 'react';

export const MyComponent = () => {
  useEffect(() => {
    const sdk = createAppIframeSDK({
      onMessage: {},
    });

    // Use / Save a reference to the sdk here.
    // For example you may want to pass it down in a react context.

    return () => {
      sdk._cleanupTransport();
    };
  }, []);
};

NextJS Example

Since by default NextJS is a server-side rendering framework, you will need to mount the SDK in a "client" component. For example, you can mount the SDK in a "client" layout defined at the root of your app.

File structure:

app/
    layout.tsx
    layout.client.tsx
lib/
    iframe.ts

Root layout:

// app/layout.tsx
import { ClientLayout } from './layout.client';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <ClientLayout>{children}</ClientLayout>
      </body>
    </html>
  );
}

NextJS will mount this code on the client:

// app/layout.client.tsx

'use client'; // this line is important
import { FC, PropsWithChildren } from 'react';
import '@/lib/iframe';

export const ClientLayout: FC<PropsWithChildren> = ({ children }) => {
  return <>{children}</>;
};

We declare the SDK in a separate file, such that we can import it in throughout the rest of the app and use it.

// lib/iframe.ts
import { createAppIframeSDK } from '@whop-apps/sdk';

export const WhopApp = createAppIframeSDK({
  onMessage: {},
});

API

These functions are available to you once you have created the SDK.

inAppPurchase

Using an async call you can open a modal in the whop application that allows the user to purchase a "line item" you have defined. It returns with a session_id and receipt_id that you can use to verify the purchase on your backend.

Line Items

In order to offer in-app purchases you must first define a line item. A line item is a purchasable entity that you can create / update / delete using the whop API. It stores the price, name and currency of your in-app purchase. A line item is also scoped to a company. This means that when opening a purchase modal, you must ensure that the line_item_id you are passing in was created with respect to the the same company that the user is currently on. For example:

  • Your app is installed on biz_1 and biz_2
  • Your app must create 2 line items, one for biz_1 and one for biz_2:
    • li_1 for biz_1
    • li_2 for biz_2
  • Alice is on viewing a product purchased from biz_1 on whop.com
  • When calling the inAppPurchase function, you must pass in the line_item_id created for biz_1: li_1
  • If you pass in li_2 the purchase modal will throw an error.

This is done so that you can allow sellers of your app to define custom prices for the various products they are selling through your app. For example: If your app allows sellers to sell files, you can allow sellers to define the price of each file they are selling. Do this by creating line items for each file, scoped to the company_id of the seller.

See the API reference for detailed information on how to create line items using the API SDK or the API directly.

Usage:

import { WhopApp } from '@/lib/iframe';

const purchase = await WhopApp.inAppPurchase({
  line_item_id: 'li_XXXXXX',
  quantity: 1, // Optional, defaults to 1. Only works if the line item supports multiple quantities.
});

type Return =
  | {
      status: 'ok';
      data: {
        session_id: string;
        receipt_id: string;
      };
    }
  | {
      status: 'error';
      error: string;
    };

Considerations:

  • The returned session_id is a unique identifier for the purchase session. The receipt_id is the unique identifier for the purchase itself. In order to correctly verify the purchase you should verify either on your server using the whop api.
  • Currently, this function is not implemented in all iframe views. It is only designed to work on the customer_product_view and seller_product_view views. If you call this function on any other view, it will return an error.

getIframeViewKey

Since your app may be shown in multiple places in the whop application, this function allows you to determine where your app is currently being shown.

Usage:

import { WhopApp } from '@/lib/iframe';

const viewKey = await WhopApp.getIframeViewKey();

type Return = {
  type:
    | 'customer_product_view',
    | 'seller_product_view',
    | 'seller_view',
    | 'customer_before_checkout_view',
};

getRedirectUrl

Since your app is embedded within an iframe on whop, you can not easily create a link that will open your app on a certain page, since you must know both the url on your site (easy) AND the url the user is currently viewing on whop.com (impossible).

This function will create a special link that links to the current page the user is viewing on whop.com. When your app is opened, it will not be sent to the default iframe location you have defined on the web UI, but rather the path parameter defined in the call to this function.

Usage:

import { WhopApp } from '@/lib/iframe';

const redirectUrl = await WhopApp.getRedirectUrl({
  path: `/relative/path/on/my/domain`,
});

type Return = {
  url: string;
};

openExternalUrl

Calling this function will open the given url in the current browser window, effectively closing your app, and whop.com. This allows you to navigate away from whop.com to a different website.

This, in combination with the getRedirectUrl function is useful for initiating OAuth flows.

Usage:

import { WhopApp } from '@/lib/iframe';

await WhopApp.openExternalUrl({ url: 'https://google.com' });

Inverse Communication

In addition to your app making requests out to the whop application, the whop app may also make requests to your app. Handling these messages is currently optional, but can greatly improve the user experience of your app.

To handle these messages, define a handler for the message type within the onMessage object, which is passed to the createAppIframeSDK function.

Your handler may be an async function. The SDK will await the result of your handler, and send it back to the whop application. The functions are typed such that the return value of your handler must match the type of the message you are handling. Whop will validate the data, and drop messages that do not match the expected type.

import { createAppIframeSDK } from '@whop-apps/sdk';

export const WhopApp = createAppIframeSDK({
  onMessage: {
    someEventType: async (data) => {
      // Handle the message here
      // return some optional value
    },
  },
});

Available message types to handle:

onCheckoutRequirementComplete

When showing your app in the customer_before_checkout_view the user may be required to complete some action before you want to allow them to checkout your product. For example they may be required to fill out some form fields.

The whop UI renders a "Continue" button that, when clicked, will message your app with the onCheckoutRequirementComplete message type.

Here you can validate/save the user's inputs and return a boolean that indicates whether or not the user should be allowed to continue to checkout. You may also respond with an error message that will be shown to the user as a toast.

Usage:

import { WhopApp } from '@/lib/iframe';

export const WhopApp = createAppIframeSDK({
  onMessage: {
    onCheckoutRequirementComplete: async (data) => {
      // data contains the following:
      const { checkoutSessionId, planId, productId, companyId } = data;

      return {
        shouldContinue: true,
        errorMessage: 'Some error message', // Optional
      };
    },
  },
});

Advanced Usage:

If you want to (for example) display a multi page form, you can return a shouldContinue value of false the first time the user clicks the button, and instead navigate to the second page of your form. Then when the user clicks the button again, you can return true and allow the user to continue to checkout.

Considerations:

  • You must respond to this message within 800ms of receiving it. If you do not, the whop UI will assume that the user should be allowed to continue to checkout.

Advanced Usage

While this functionality is not intended for regular SDK usage, it's documented here for thoroughness.

  • You can pass an extra parameter to createAppIframeSDK called overrideParentOrigins. This should be an array of origins that are rendering your iframe. If the host app (ie: whop.com) is not running on whop.com or dash.whop.com in the browser, you must pass this parameter in order to allow communication between your app and the host app. This is useful when running the whop app in a staging environment and testing against an app through the localhost/staging version of whop.com.

  • The SDK also exports an "inverse" function to createAppIframeSDK called createWhopIframeSDK. This function behaves exactly like the former, however it implements the other side of the communication protocol. It is used by the whop host application to enable 2 way communication with type safety.

  • The SDK is backed by @whop-apps/typed-transport which is a library that provides a type safe interface for 2 parties to communicate over a 2 way message channel in an async request/response style. It abstracts validation, message timeouts and more. It allows the developer to define the transport layer (in this case window.postMessage) and the message types. It is used by both the SDK and the whop host application to communicate with each other.

0.0.1-canary.60

8 days ago

0.0.1-canary.59

24 days ago

0.0.1-canary.58

25 days ago

0.0.1-canary.57

25 days ago

0.0.1-canary.56

25 days ago

0.0.1-canary.55

1 month ago

0.0.1-canary.54

1 month ago

0.0.1-canary.52

1 month ago

0.0.1-canary.53

1 month ago

0.0.1-canary.51

1 month ago

0.0.1-canary.50

2 months ago

0.0.1-canary.49

2 months ago

0.0.1-canary.48

2 months ago

0.0.1-canary.47

2 months ago

0.0.1-canary.46

2 months ago

0.0.1-canary.41

3 months ago

0.0.1-canary.45

3 months ago

0.0.1-canary.44

3 months ago

0.0.1-canary.43

3 months ago

0.0.1-canary.42

3 months ago

0.0.1-canary.40

3 months ago

0.0.1-canary.39

3 months ago

0.0.1-canary.38

4 months ago

0.0.1-canary.37

5 months ago

0.0.1-canary.34

5 months ago

0.0.1-canary.33

5 months ago

0.0.1-canary.36

5 months ago

0.0.1-canary.35

5 months ago

0.0.1-canary.32

5 months ago

0.0.1-canary.31

5 months ago

0.0.1-canary.30

6 months ago

0.0.1-canary.29

6 months ago

0.0.1-canary.28

6 months ago

0.0.1-canary.27

6 months ago

0.0.1-canary.26

7 months ago

0.0.1-canary.25

7 months ago

0.0.1-canary.24

7 months ago

0.0.1-canary.23

7 months ago

0.0.1-canary.22

7 months ago

0.0.1-canary.21

7 months ago

0.0.1-canary.20

7 months ago

0.0.1-canary.19

7 months ago

0.0.1-canary.18

7 months ago

0.0.1-canary.17

7 months ago

0.0.1-canary.16

7 months ago

0.0.1-canary.15

7 months ago

0.0.1-canary.14

7 months ago

0.0.1-canary.13

7 months ago

0.0.1-canary.12

8 months ago

0.0.1-canary.11

8 months ago

0.0.1-canary.10

8 months ago

0.0.1-canary.9

8 months ago

0.0.1-canary.8

8 months ago

0.0.1-canary.7

8 months ago

0.0.1-canary.6

8 months ago

0.0.1-canary.5

8 months ago

0.0.1-canary.4

8 months ago

0.0.1-canary.3

8 months ago

0.0.1-canary.2

8 months ago

0.0.1-canary.1

8 months ago

0.0.1-canary.0

9 months ago