diff --git a/AGENTS.md b/AGENTS.md index 0e1e7f5..9e1895a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,9 +13,13 @@ For documentation-writing guidelines (tone, content conventions, etc.), see `con ## Non-negotiable rules - **Never deploy without explicit user approval.** -- **Edit source docs only**: update files in `content/docs/**` (and `content/shared/**` if used). - **Never edit `public/**`** (it is generated and will be overwritten). +- **Content vs. engineering changes**: + - **Content changes**: update files in `content/docs/**` (and `content/shared/**` if used). + - **Engineering changes**: you may edit other parts of the repo as needed. + - **Never edit `public/**`** (it is generated and will be overwritten). - **SDK references first**: if a task needs SDK APIs/changelogs, run `bun run download:references` before writing. +- **SDK reference tables**: use the Fumadocs-style `TypeTable` for parameters/types; do not use ``. +- **Test**: run `bun run build:cf` and `bun test` after making changes to ensure you didn't break anything ## Common commands (run from repo root) ```bash @@ -124,4 +128,4 @@ This clones/pulls SDK repos into `reference/` (gitignored). Use the source to co ## Troubleshooting - **Build failures**: check MDX syntax; validate `meta.json`; ensure listed pages exist; run `bun run build` for full errors. -- **Dev server issues**: `rm -rf .next`, `bun run clear:cache`, verify port 8293 is free, reinstall dependencies if needed. \ No newline at end of file +- **Dev server issues**: `rm -rf .next`, `bun run clear:cache`, verify port 8293 is free, reinstall dependencies if needed. diff --git a/bun.lock b/bun.lock index 7759fd9..74da8be 100644 --- a/bun.lock +++ b/bun.lock @@ -7,6 +7,7 @@ "dependencies": { "@ai-sdk/react": "^2.0.109", "@aws-sdk/client-s3": "^3.948.0", + "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.11", "ai": "^5.0.108", "class-variance-authority": "^0.7.1", diff --git a/cli.json b/cli.json new file mode 100644 index 0000000..89d5151 --- /dev/null +++ b/cli.json @@ -0,0 +1,13 @@ +{ + "$schema": "node_modules/@fumadocs/cli/dist/schema/src.json", + "aliases": { + "uiDir": "./components/ui", + "componentsDir": "./components", + "blockDir": "./components", + "cssDir": "./styles", + "libDir": "./lib" + }, + "baseDir": "src", + "uiLibrary": "radix-ui", + "commands": {} +} \ No newline at end of file diff --git a/content/docs/android/sdk-reference/PaywallOptions.mdx b/content/docs/android/sdk-reference/PaywallOptions.mdx index d03186d..fa8b7ea 100644 --- a/content/docs/android/sdk-reference/PaywallOptions.mdx +++ b/content/docs/android/sdk-reference/PaywallOptions.mdx @@ -40,24 +40,80 @@ class PaywallOptions { ``` ## Parameters - -| Property | Type | Description | -|----------|------|-------------| -| `isHapticFeedbackEnabled` | `Boolean` | Enables haptic feedback when users purchase/restore, open links, or close the paywall. Defaults to `true`. | -| `restoreFailed` | `RestoreFailed` | Messaging for the restore-failed alert. | -| `restoreFailed.title` | `String` | Title for restore-failed alert. Defaults to "No Subscription Found". | -| `restoreFailed.message` | `String` | Message for restore-failed alert. Defaults to "We couldn't find an active subscription for your account." | -| `restoreFailed.closeButtonTitle` | `String` | Close button title for restore-failed alert. Defaults to "Okay". | -| `shouldShowPurchaseFailureAlert` | `Boolean` | Shows an alert after a purchase fails. Set to `false` if you handle failures via a `PurchaseController`. Defaults to `true`. | -| `shouldPreload` | `Boolean` | Preloads and caches trigger paywalls and products during SDK initialization. Set to `false` for just-in-time loading. Defaults to `true`. | -| `useCachedTemplates` | `Boolean` | Loads paywall template websites from disk when available. Defaults to `false`. | -| `automaticallyDismiss` | `Boolean` | Automatically dismisses the paywall on successful purchase or restore. Defaults to `true`. | -| `transactionBackgroundView` | `TransactionBackgroundView?` | View shown behind the system payment sheet during a transaction. Use `null` for no view. Defaults to `.SPINNER`. | -| `overrideProductsByName` | `Map` | Overrides products on all paywalls using name→identifier mapping (e.g., `"primary"` → `"com.example.premium_monthly"`). | -| `optimisticLoading` | `Boolean` | Hides shimmer optimistically. Defaults to `false`. | -| `timeoutAfter` | `Duration?` | Duration until a paywall timeout is invoked. When not using fallback loading, setting this triggers a timeout instead of retrying. | -| `onBackPressed` | `((PaywallInfo?) -> Boolean)?` | Callback invoked when back button is pressed (requires `reroute_back_button` enabled in paywall settings). Return `true` to consume the back press, `false` to use SDK default behavior. Defaults to `null`. | - +", + description: "Overrides products on all paywalls using name\u2192identifier mapping (e.g., `\"primary\"` \u2192 `\"com.example.premium_monthly\"`).", + required: true, + }, + optimisticLoading: { + type: "Boolean", + description: "Hides shimmer optimistically.", + default: "false", + }, + timeoutAfter: { + type: "Duration?", + description: "Duration until a paywall timeout is invoked. When not using fallback loading, setting this triggers a timeout instead of retrying.", + }, + onBackPressed: { + type: "((PaywallInfo?) -> Boolean)?", + description: "Callback invoked when back button is pressed (requires `reroute_back_button` enabled in paywall settings). Return `true` to consume the back press, `false` to use SDK default behavior.", + default: "null", + }, + }} +/> + ## Usage ```kotlin diff --git a/content/docs/android/sdk-reference/PurchaseController.mdx b/content/docs/android/sdk-reference/PurchaseController.mdx index 1e0db02..f75898c 100644 --- a/content/docs/android/sdk-reference/PurchaseController.mdx +++ b/content/docs/android/sdk-reference/PurchaseController.mdx @@ -39,12 +39,21 @@ public interface PurchaseController { ``` ## Parameters - -| Method | Parameters | Return Type | Description | -|--------|------------|-------------|-------------| -| `purchase` | `activity: Activity`, `product: StoreProduct` | `PurchaseResult` | Called when user initiates purchasing. Implement your purchase logic here. Activity is needed for Google Play Billing. | -| `restorePurchases` | None | `RestorationResult` | Called when user initiates restore. Implement your restore logic here. | - + + ## Returns / State - `purchase()` returns a `PurchaseResult` (`.Purchased`, `.Failed(Throwable)`, `.Cancelled`, or `.Pending`) @@ -54,4 +63,4 @@ When using a PurchaseController, you must also manage [`subscriptionStatus`](/an ## Usage -For implementation examples and detailed guidance, see [Using RevenueCat](/android/guides/using-revenuecat). \ No newline at end of file +For implementation examples and detailed guidance, see [Using RevenueCat](/android/guides/using-revenuecat). diff --git a/content/docs/android/sdk-reference/SuperwallDelegate.mdx b/content/docs/android/sdk-reference/SuperwallDelegate.mdx index 043f0aa..45cc0ee 100644 --- a/content/docs/android/sdk-reference/SuperwallDelegate.mdx +++ b/content/docs/android/sdk-reference/SuperwallDelegate.mdx @@ -67,17 +67,46 @@ public interface SuperwallDelegateJava { ## Parameters All methods are optional to implement. Key methods include: - -| Method | Parameters | Description | -|--------|------------|-------------| -| `subscriptionStatusDidChange` | `from`, `to` | Called when subscription status changes. | -| `handleSuperwallEvent` | `eventInfo` | Called for all internal analytics events. Use for tracking in your own analytics. | -| `handleCustomPaywallAction` | `name` | Called when user taps elements with `data-pw-custom` tags. | -| `willPresentPaywall` | `paywallInfo` | Called before paywall presentation. | -| `didPresentPaywall` | `paywallInfo` | Called after paywall presentation. | -| `willDismissPaywall` | `paywallInfo` | Called before paywall dismissal. | -| `didDismissPaywall` | `paywallInfo` | Called after paywall dismissal. | - + + ## Returns / State All delegate methods return `Unit`. They provide information about Superwall events and state changes. @@ -172,4 +201,4 @@ public class MainActivity extends AppCompatActivity implements SuperwallDelegate updateUI(to); } } -``` \ No newline at end of file +``` diff --git a/content/docs/android/sdk-reference/SuperwallOptions.mdx b/content/docs/android/sdk-reference/SuperwallOptions.mdx index 07dc934..5f10af0 100644 --- a/content/docs/android/sdk-reference/SuperwallOptions.mdx +++ b/content/docs/android/sdk-reference/SuperwallOptions.mdx @@ -47,20 +47,61 @@ public class SuperwallOptions { ``` ## Parameters - -| Property | Type | Description | -|----------|------|-------------| -| `paywalls` | [`PaywallOptions`](/android/sdk-reference/PaywallOptions) | Controls paywall presentation, preload, and alert behavior. | -| `shouldObservePurchases` | `Boolean` | Set to `true` to have Superwall observe Google Play purchases you make outside the SDK. | -| `networkEnvironment` | `NetworkEnvironment` | Overrides the API environment. **Only change if instructed by Superwall.** | -| `isExternalDataCollectionEnabled` | `Boolean` | Allows Superwall to send non-paywall analytics events to the backend. Defaults to `true`. | -| `localeIdentifier` | `String?` | Overrides the locale used for rule evaluation (e.g., `"en_GB"`). | -| `isGameControllerEnabled` | `Boolean` | Forwards game controller events to paywalls. | -| `passIdentifiersToPlayStore` | `Boolean` | When `true`, Google Play receives the raw `userId` as `obfuscatedExternalAccountId`; otherwise Superwall sends a SHA-256 hash. | -| `enableExperimentalDeviceVariables` | `Boolean` | Enables experimental device variables (subject to change). | -| `logging` | `Logging` | Sets log level and scopes printed to Logcat. | -| `useMockReviews` | `Boolean` | Shows mock Google Play review dialogs for testing. | - + + ## How `passIdentifiersToPlayStore` affects Google Play Superwall always calls `BillingFlowParams.Builder.setObfuscatedAccountId(Superwall.instance.externalAccountId)` when launching a billing flow. @@ -84,4 +125,4 @@ Superwall.configure( ``` ## Related -- [`PaywallOptions`](/android/sdk-reference/PaywallOptions) \ No newline at end of file +- [`PaywallOptions`](/android/sdk-reference/PaywallOptions) diff --git a/content/docs/android/sdk-reference/advanced/PaywallBuilder.mdx b/content/docs/android/sdk-reference/advanced/PaywallBuilder.mdx index 7ddd43b..cc9ad84 100644 --- a/content/docs/android/sdk-reference/advanced/PaywallBuilder.mdx +++ b/content/docs/android/sdk-reference/advanced/PaywallBuilder.mdx @@ -29,15 +29,34 @@ class PaywallBuilder(private val placement: String) { ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `placement` | `String` | The name of the placement as defined on the Superwall dashboard. | -| `params` | `Map?` | Optional parameters to pass with your placement for audience filters. Keys beginning with `$` are reserved and will be dropped. | -| `overrides` | `PaywallOverrides?` | Optional overrides for products and presentation style. | -| `delegate` | `PaywallViewCallback` | A delegate to handle user interactions with the retrieved PaywallView. | -| `activity` | `Activity` | The activity context required for the PaywallView. | - +?", + description: "Optional parameters to pass with your placement for audience filters. Keys beginning with `$` are reserved and will be dropped.", + }, + overrides: { + type: "PaywallOverrides?", + description: "Optional overrides for products and presentation style.", + }, + delegate: { + type: "PaywallViewCallback", + description: "A delegate to handle user interactions with the retrieved PaywallView.", + required: true, + }, + activity: { + type: "Activity", + description: "The activity context required for the PaywallView.", + required: true, + }, + }} +/> + ## Returns / State Returns a `Result` that you can add to your view hierarchy. If presentation should be skipped, returns a failure result. diff --git a/content/docs/android/sdk-reference/advanced/setSubscriptionStatus.mdx b/content/docs/android/sdk-reference/advanced/setSubscriptionStatus.mdx index 731feae..a06cf19 100644 --- a/content/docs/android/sdk-reference/advanced/setSubscriptionStatus.mdx +++ b/content/docs/android/sdk-reference/advanced/setSubscriptionStatus.mdx @@ -25,11 +25,16 @@ public void setSubscriptionStatus(SubscriptionStatus status) ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `status` | `SubscriptionStatus` | The subscription status to set. Can be `SubscriptionStatus.Unknown`, `SubscriptionStatus.Active(entitlements)`, or `SubscriptionStatus.Inactive`. | - + + ## Returns / State This function returns `Unit`. The new status will be reflected in the [`subscriptionStatus`](/android/sdk-reference/subscriptionStatus) StateFlow and will trigger the [`SuperwallDelegate.subscriptionStatusDidChange`](/android/sdk-reference/SuperwallDelegate) callback. diff --git a/content/docs/android/sdk-reference/configure.mdx b/content/docs/android/sdk-reference/configure.mdx index ada5fb6..2bfef0b 100644 --- a/content/docs/android/sdk-reference/configure.mdx +++ b/content/docs/android/sdk-reference/configure.mdx @@ -10,106 +10,70 @@ This is a static method called on the `Superwall` class itself, not on the share ## Purpose Configures the shared instance of Superwall with your API key and optional configurations, making it ready for use throughout your Android app. -## Signature - - - -```swift iOS -public static func configure( - apiKey: String, - purchaseController: PurchaseController? = nil, - options: SuperwallOptions? = nil, - completion: (() -> Void)? = nil -) -> Superwall -``` - ```kotlin Android public fun configure( - application: Application, + applicationContext: Application, apiKey: String, purchaseController: PurchaseController? = null, options: SuperwallOptions? = null, - completion: (() -> Unit)? = null + activityProvider: ActivityProvider? = null, + completion: ((Result) -> Unit)? = null ) ``` -```dart Flutter -static Future configure( - String apiKey, { - SuperwallOptions? options, - PurchaseController? purchaseController, -}) -``` - -```typescript React Native -static configure( - apiKey: string, - purchaseController?: PurchaseController, - options?: SuperwallOptions -): Promise -``` - - - ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `application` | `Application` | Your Android Application instance, required for SDK initialization and lifecycle management. | -| `apiKey` | `String` | Your Public API Key from the Superwall dashboard settings. | -| `purchaseController` | `PurchaseController?` | Optional object for handling all subscription-related logic yourself. If `null`, Superwall handles subscription logic. Defaults to `null`. | -| `options` | `SuperwallOptions?` | Optional configuration object for customizing paywall appearance and behavior. See [`SuperwallOptions`](/android/sdk-reference/SuperwallOptions) for details. Defaults to `null`. | -| `completion` | `(() -> Unit)?` | Optional completion handler called when Superwall finishes configuring. Defaults to `null`. | - +) -> Unit)?", + description: "Optional completion handler called when Superwall finishes configuring. Result contains success or failure.", + default: "null", + }, + }} +/> + ## Returns / State Configures the Superwall instance which is accessible via [`Superwall.instance`](/android/sdk-reference/Superwall). -## Usage - - - -```swift iOS -// AppDelegate or SceneDelegate -func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - Superwall.configure(apiKey: "pk_your_api_key") - return true -} -``` - ```kotlin Android class MyApplication : Application() { override fun onCreate() { super.onCreate() Superwall.configure( - application = this, + applicationContext = this, apiKey = "pk_your_api_key" ) } } ``` -```dart Flutter -void main() async { - WidgetsFlutterBinding.ensureInitialized(); - await Superwall.configure("pk_your_api_key"); - runApp(MyApp()); -} -``` - -```typescript React Native -// App.tsx -export default function App() { - useEffect(() => { - Superwall.configure("pk_your_api_key"); - }, []); - - return ; -} -``` - - - With custom options: ```kotlin class MyApplication : Application() { @@ -121,7 +85,7 @@ class MyApplication : Application() { } Superwall.configure( - application = this, + applicationContext = this, apiKey = "pk_your_api_key", options = options ) { @@ -129,4 +93,4 @@ class MyApplication : Application() { } } } -``` \ No newline at end of file +``` diff --git a/content/docs/android/sdk-reference/getPresentationResult.mdx b/content/docs/android/sdk-reference/getPresentationResult.mdx index fec57ad..4511e1a 100644 --- a/content/docs/android/sdk-reference/getPresentationResult.mdx +++ b/content/docs/android/sdk-reference/getPresentationResult.mdx @@ -26,12 +26,21 @@ fun Superwall.getPresentationResultSync( ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `placement` | `String` | The placement to evaluate. | -| `params` | `Map?` | Optional custom parameters that feed audience filters. Keys starting with `$` are dropped by the SDK. Nested maps or lists are ignored. Defaults to `null`. | - +?", + description: "Optional custom parameters that feed audience filters. Keys starting with `$` are dropped by the SDK. Nested maps or lists are ignored.", + default: "null", + }, + }} +/> + ## Returns / State Returns a Kotlin `Result`. diff --git a/content/docs/android/sdk-reference/handleDeepLink.mdx b/content/docs/android/sdk-reference/handleDeepLink.mdx index 79b1e4e..4b648b8 100644 --- a/content/docs/android/sdk-reference/handleDeepLink.mdx +++ b/content/docs/android/sdk-reference/handleDeepLink.mdx @@ -25,11 +25,16 @@ public void handleDeepLink(String url) ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `url` | `String` | The deep link URL to process for paywall triggers. | - + + ## Returns / State This function returns `Unit`. If the URL matches a campaign configured on the dashboard, it may trigger a paywall presentation. diff --git a/content/docs/android/sdk-reference/identify.mdx b/content/docs/android/sdk-reference/identify.mdx index ec79465..409c8ac 100644 --- a/content/docs/android/sdk-reference/identify.mdx +++ b/content/docs/android/sdk-reference/identify.mdx @@ -27,12 +27,21 @@ public void identify( ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `userId` | `String` | Your user's unique identifier, as defined by your backend system. | -| `options` | `IdentityOptions?` | Optional configuration for identity behavior. Set `restorePaywallAssignments` to `true` to wait for paywall assignments from the server. Use only in advanced cases where users frequently switch accounts. Defaults to `null`. | - + + ## Returns / State This function returns `Unit`. After calling, [`isLoggedIn`](/android/sdk-reference/userId) will return `true` and [`userId`](/android/sdk-reference/userId) will return the provided user ID. diff --git a/content/docs/android/sdk-reference/register.mdx b/content/docs/android/sdk-reference/register.mdx index 404a64f..2c7801f 100644 --- a/content/docs/android/sdk-reference/register.mdx +++ b/content/docs/android/sdk-reference/register.mdx @@ -25,14 +25,31 @@ fun Superwall.register( ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `placement` | `String` | The name of the placement you wish to register. | -| `params` | `Map?` | Optional parameters to pass with your placement. These can be referenced within campaign rules. Keys beginning with `$` are reserved for Superwall and will be dropped. Arrays and nested maps are currently unsupported and will be ignored. Defaults to `null`. | -| `handler` | `PaywallPresentationHandler?` | A handler whose functions provide status updates for the paywall lifecycle. Defaults to `null`. | -| `feature` | `() -> Unit` | A completion block representing the gated feature. It is executed based on the paywall's gating mode: called immediately for **Non-Gated**, called after the user subscribes or if already subscribed for **Gated**. | - +?", + description: "Optional parameters to pass with your placement. These can be referenced within campaign rules. Keys beginning with `$` are reserved for Superwall and will be dropped. Arrays and nested maps are currently unsupported and will be ignored.", + default: "null", + }, + handler: { + type: "PaywallPresentationHandler?", + description: "A handler whose functions provide status updates for the paywall lifecycle.", + default: "null", + }, + feature: { + type: "() -> Unit", + description: "A completion block representing the gated feature. It is executed based on the paywall's gating mode: called immediately for **Non-Gated**, called after the user subscribes or if already subscribed for **Gated**.", + required: true, + }, + }} +/> + ## Returns / State This function returns `Unit`. If you supply a `feature` lambda, it will be executed according to the paywall's gating configuration, as described above. diff --git a/content/docs/android/sdk-reference/setUserAttributes.mdx b/content/docs/android/sdk-reference/setUserAttributes.mdx index 3cfae31..2aa7b9f 100644 --- a/content/docs/android/sdk-reference/setUserAttributes.mdx +++ b/content/docs/android/sdk-reference/setUserAttributes.mdx @@ -25,11 +25,16 @@ public void setUserAttributes(Map attributes) ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `attributes` | `Map` | A map of custom attributes to store for the user. Values can be any JSON encodable value, including strings, numbers, booleans, URLs, or timestamps. | - +", + description: "A map of custom attributes to store for the user. Values can be any JSON encodable value, including strings, numbers, booleans, URLs, or timestamps.", + required: true, + }, + }} +/> + ## Returns / State This function returns `Unit`. If an attribute already exists, its value will be overwritten while other attributes remain unchanged. diff --git a/content/docs/dashboard/dashboard-campaigns/campaigns-standard-placements.mdx b/content/docs/dashboard/dashboard-campaigns/campaigns-standard-placements.mdx index 7a821dd..92c83a7 100644 --- a/content/docs/dashboard/dashboard-campaigns/campaigns-standard-placements.mdx +++ b/content/docs/dashboard/dashboard-campaigns/campaigns-standard-placements.mdx @@ -4,19 +4,176 @@ title: "Standard Placements" Standard placements are events that Superwall automatically manages. The following [Superwall Events](/tracking-analytics) are registered by the SDK and can be added as placements in campaigns to present paywalls: -- `app_install` -- `app_launch` -- `deepLink_open` -- `session_start` -- `paywall_decline` -- `transaction_fail` -- `transaction_abandon` -- `survey_response` -- `touches_began` +- [`app_install`](#app_install) +- [`app_launch`](#app_launch) +- [`deepLink_open`](#deeplink_open) +- [`session_start`](#session_start) +- [`paywall_decline`](#paywall_decline) +- [`transaction_fail`](#transaction_fail) +- [`transaction_abandon`](#transaction_abandon) +- [`survey_response`](#survey_response) +- [`touches_began`](#touches_began) -Visit [Superwall Events](/tracking-analytics) to see a full list of parameters that you can use with these events. Here are a few examples of how they might be used: +## `app_install` -## Using the `paywall_decline` event +### Usage + +This is registered when the SDK is configured for the first time. Use it for first-launch onboarding flows or one-time offers. + +### Parameters + +These parameters are always available: + + + +## `app_launch` + +### Usage + +This is registered when the app is launched from a cold start. Use it to present paywalls on fresh launches. + +### Parameters + +Same as `app_install`: + + + +## `deepLink_open` + +### Usage + +This is registered when a user opens the app via a deep link. First, you need to make sure to [tell Superwall when a deep link has been opened](/in-app-paywall-previews). + +You can use the URL parameters of the deep link within your rules. This works for both URL schemes and universal links. + +For example, you could make three conditions to match this deep link: `myapp://paywall?offer=July20`. Here's how: + +1. Add a rule to see if the event is `deepLink_open`. See the `paywall_decline` example below for how to add a standard placement. +2. Add `params.offer` is equal to whatever you've made, like `July20` for a timeboxed offer you made in that month. +3. Then, you'd also add `params.path` is equal to the text of a path you setup, like `paywall`. + +### Parameters + +After the app has emitted the first `deepLink_open` event for a given URL, these fields become available to audience filters: + +': { + description: + 'Optional. Any query string parameter (for example, `params.offer` for `?offer=July20`).', + type: 'string', + }, + }} +/> + +## `session_start` + +### Usage + +This is registered when the app is opened after at least 60 minutes since the last `app_close`. + +### Parameters + +Same as `app_install`: + + + +## `paywall_decline` + +### Usage This is registered when a user manually dismisses any paywall. You can combine this with rules to show a paywall when a user closes a specific paywall. First, [add](/campaigns-placements#adding-a-placement) the standard placement to a campaign: @@ -30,7 +187,234 @@ Here, when a user closes the paywall named `PaywallA`, a new paywall will show. Note that you can't reference parameters that you've passed in to your original register call in your rules for `paywall_decline`. -## Using the `survey_response` event +### Parameters + +Audience filters for `paywall_decline` placements can use the following parameters (empty values mean the field isn't applicable): + +_product_id': { + description: + 'Product identifier keyed by the product name (for example, `annual_product_id`).', + type: 'string', + required: true, + }, + is_free_trial_available: { + description: + 'True when any introductory offer is available (including both free and paid trials).', + type: 'boolean', + required: true, + }, + feature_gating: { + description: 'Feature gating behavior for the paywall.', + type: 'string', + required: true, + }, + }} +/> + +## `transaction_fail` + +### Usage + +This is registered when the payment sheet fails to complete a transaction (this does not include user cancellation). Use it to show an exit offer after a failed attempt. + +### Parameters + +Audience filters for `transaction_fail` placements can use the following parameters (empty values mean the field isn't applicable): + +_product_id': { + description: + 'Product identifier keyed by the product name (for example, `annual_product_id`).', + type: 'string', + required: true, + }, + is_free_trial_available: { + description: + 'True when any introductory offer is available (including both free and paid trials).', + type: 'boolean', + required: true, + }, + feature_gating: { + description: 'Feature gating behavior for the paywall.', + type: 'string', + required: true, + }, + }} +/> + +The event payload also includes a failure `message`; see [Superwall Events](/tracking-analytics) for full details. + +## `transaction_abandon` + +### Usage + +This is registered when a user dismisses the store purchase sheet before the transaction completes. If a transaction-abandon paywall matches, Superwall immediately closes the current paywall and presents the new one. + +### Parameters + +Audience filters for `transaction_abandon` placements can use the following parameters (empty values mean the field isn't applicable): + +_product_id': { + description: + 'Product identifier keyed by the product name (for example, `annual_product_id`).', + type: 'string', + required: true, + }, + is_free_trial_available: { + description: + 'True when any introductory offer is available (including both free and paid trials).', + type: 'boolean', + required: true, + }, + feature_gating: { + description: 'Feature gating behavior for the paywall.', + type: 'string', + required: true, + }, + }} +/> + +For example, to show a transaction-abandon paywall only for onboarding paywalls, add a `transaction_abandon` placement and set `presented_by_event_name` to `onboarding`. To limit it to a single paywall, add `paywall_id` as an additional condition. + +## `survey_response` + +### Usage This is registered when a response to a paywall survey has been recorded. First, you need to make sure your paywall [has a survey attached](/surveys). @@ -38,23 +422,67 @@ You can combine this with rules to show a paywall whenever a survey response is For example, if the user selected a survey option named `Too Expensive`, you could present another paywall with a discounted option. This is a great opportunity to show a discounted paywall to improve your conversion rate. -## Using the `deepLink_open` event +### Parameters -This is registered when a user opens the app via a deep link. First, you need to make sure to [tell Superwall when a deep link has been opened](/in-app-paywall-previews). +Audience filters for `survey_response` placements can use the following parameters (empty values mean the field isn't applicable): -You can use the URL parameters of the deep link within your rules. This works for both URL schemes and universal links. After the app has emitted the first `deepLink_open` event for a given URL, these fields become available to audience filters: + -- `params.url` -- `params.path` -- `params.host` -- `params.query` -- `params.pathExtension` -- `params.lastPathComponent` -- `params.fragment` -- All query string parameters (for example, `params.offer` for `?offer=July20`) +## `touches_began` -For example, you could make three conditions to match this deep link: `myapp://paywall?offer=July20`. Here's how: +### Usage -1. Add the a rule to see if the event is `deepLink_open`. See the [first example](/campaigns-placements#using-the-paywall-decline-event) above using `paywall_decline` to see how to do this. -2. Add `params.offer` is equal to whatever you've made, like `July20` for a timeboxed offer you made in that month. -3. Then, you'd also add `params.path` is equal to the text of a path you setup, like `paywall`. +This is registered when the user touches the app's UIWindow for the first time. It is only tracked if there is an active `touches_began` placement in a campaign. + +### Parameters + +Same as `app_install`: + + diff --git a/content/docs/expo/guides/testing-purchases.mdx b/content/docs/expo/guides/testing-purchases.mdx index ac09fc1..6fb7c77 100644 --- a/content/docs/expo/guides/testing-purchases.mdx +++ b/content/docs/expo/guides/testing-purchases.mdx @@ -3,4 +3,38 @@ title: "StoreKit testing (iOS only)" description: "How to set up StoreKit testing for iOS when using the Expo SDK." --- -../../../shared/testing-purchases.mdx +StoreKit testing in Xcode is a local test environment for testing in-app purchases without requiring a connection to App Store servers. To use it with Expo, you must run a development build and open the generated iOS project in Xcode. + +## Expo prerequisites + + + +**Expo Go is not supported** + +The Superwall SDK uses native modules that are not available in Expo Go. You must use an [Expo Development Build](https://docs.expo.dev/develop/development-builds/introduction/) to run your app with StoreKit testing. + +```bash +npx expo run:ios +``` + + + +If you don't already have an `ios` folder in your project, generate it with Expo prebuild: + +```bash +npx expo prebuild +``` + +Then open the iOS workspace in Xcode: + +```bash +xed ios +``` + + +Running `npx expo prebuild --clean` will regenerate the native project and reset Xcode scheme settings. If you use `--clean`, you'll need to reselect your StoreKit configuration file in the scheme afterward. + + +Once the iOS project is open in Xcode, follow the steps below. + +../../../shared/testing-purchases.mdx \ No newline at end of file diff --git a/content/docs/expo/guides/web-checkout/post-checkout-redirecting.mdx b/content/docs/expo/guides/web-checkout/post-checkout-redirecting.mdx index f90fdac..3092d4c 100644 --- a/content/docs/expo/guides/web-checkout/post-checkout-redirecting.mdx +++ b/content/docs/expo/guides/web-checkout/post-checkout-redirecting.mdx @@ -26,10 +26,26 @@ Redirect users to your own custom URL with purchase information: - **What you receive**: Purchase data is passed as query parameters to your URL **Query Parameters Included**: -- `app_user_id` - The user's identifier from your app -- `email` - User's email address -- `stripe_subscription_id` - The Stripe subscription ID -- Any custom placement parameters you set +", + description: "Any custom placement parameters you set.", + }, + }} +/> **Example**: ``` @@ -159,4 +175,4 @@ export class SWDelegate extends SuperwallDelegate { } } } -``` \ No newline at end of file +``` diff --git a/content/docs/expo/sdk-reference/components/CustomPurchaseControllerProvider.mdx b/content/docs/expo/sdk-reference/components/CustomPurchaseControllerProvider.mdx index bb1a3a4..c7099d8 100644 --- a/content/docs/expo/sdk-reference/components/CustomPurchaseControllerProvider.mdx +++ b/content/docs/expo/sdk-reference/components/CustomPurchaseControllerProvider.mdx @@ -39,60 +39,128 @@ export default function App() { ``` -**Important:** The `onPurchase` and `onPurchaseRestore` callbacks communicate success or failure through how the Promise resolves: +**Important:** The `onPurchase` and `onPurchaseRestore` callbacks communicate the outcome through the resolved value or an error: -- **Promise resolves normally** → Superwall records a successful purchase (counts as a conversion) -- **Promise throws an error** → Superwall records a failed/cancelled purchase +- **Return/resolve `void` or a success result** → Superwall records a successful purchase or restore +- **Return a failure/cancelled result or throw** → Superwall records a failed or cancelled outcome -You **must** throw an error for any non-successful outcome (user cancelled, payment failed, etc.). If your purchase function returns a status like `'cancelled'` or `'error'`, you need to check it and throw: +If your purchase function returns a status like `"cancelled"` or `"error"`, return a `PurchaseResult` with that type (or throw) so Superwall records the correct outcome: ```tsx onPurchase: async (params) => { const result = await yourPurchaseFunction(params.productId); - if (result !== 'success') { - throw new Error(`Purchase ${result}`); + if (result !== "success") { + return { type: "failed", error: `Purchase ${result}` }; } // Only reaches here on success }, ``` -**Why this matters:** If your callback resolves without throwing (even when the purchase failed), Superwall will incorrectly count it as a conversion. +**Why this matters:** If your callback resolves without signaling failure, Superwall will count it as a conversion. ## Props - -### `controller` - -**Type:** `CustomPurchaseControllerContext` - -An object implementing the purchase controller interface with the following methods: - -#### `onPurchase: (params: OnPurchaseParams) => Promise` - -Called when a user initiates a purchase from a paywall. The `params` object contains different properties based on the platform: - -**iOS Parameters:** -- `platform: "ios"` -- `productId: string` - The App Store product identifier -- Additional iOS-specific purchase parameters - -**Android Parameters:** -- `platform: "android"` -- `productId: string` - The Google Play product identifier -- `basePlanId?: string` - The subscription base plan ID (for subscriptions) -- `offerId?: string` - The promotional offer ID (if applicable) - -#### `onPurchaseRestore: () => Promise` - -Called when a user requests to restore previous purchases. This typically happens when users tap a "Restore Purchases" button in your paywall. - -### `children` - -**Type:** `React.ReactNode` - -The child components that will be wrapped by this provider. + + +### CustomPurchaseControllerContext + Promise", + description: "Handle a purchase and return a result or throw to signal failure.", + }, + onPurchaseRestore: { + type: "() => Promise", + description: "Handle restore purchases and return a result or throw to signal failure.", + }, + }} +/> + +### OnPurchaseParams (iOS) + + +### OnPurchaseParams (Android) + + +### PurchaseResult + + +### RestoreResult + ## Hook @@ -119,9 +187,14 @@ function MyComponent() { } ``` -**Returns:** `CustomPurchaseControllerContext | null` - -Returns the controller object passed to the provider, or `null` if the component is not within a `CustomPurchaseControllerProvider`. + ## How It Works @@ -138,4 +211,4 @@ For a complete RevenueCat integration with error handling, subscription status s ## Notes - The provider must wrap your app at a level where both the Superwall SDK and your purchase logic can access it -- Purchase success/failure handling is automatic - you just need to perform the actual purchase \ No newline at end of file +- Purchase success/failure handling is automatic - you just need to perform the actual purchase diff --git a/content/docs/expo/sdk-reference/components/SuperwallError.mdx b/content/docs/expo/sdk-reference/components/SuperwallError.mdx index 100b48f..addb9e3 100644 --- a/content/docs/expo/sdk-reference/components/SuperwallError.mdx +++ b/content/docs/expo/sdk-reference/components/SuperwallError.mdx @@ -7,12 +7,15 @@ title: "SuperwallError" The component can accept either static React nodes or a render function that receives the error message string. ## Props - -### `children` - -The content to render when Superwall has an error. Can be: -- Static React nodes (e.g., `Error message`) -- A function that receives the error message string and returns React nodes + React.ReactNode)", + description: "Static UI or a render function that receives the error message.", + required: true, + }, + }} +/> ## Example diff --git a/content/docs/expo/sdk-reference/components/SuperwallLoaded.mdx b/content/docs/expo/sdk-reference/components/SuperwallLoaded.mdx index 24c257d..f44a5be 100644 --- a/content/docs/expo/sdk-reference/components/SuperwallLoaded.mdx +++ b/content/docs/expo/sdk-reference/components/SuperwallLoaded.mdx @@ -7,10 +7,15 @@ title: "SuperwallLoaded" If Superwall is still loading, has not been configured, or has a configuration error, this component will render `null`. ## Props - -### `children` - -The content to render once Superwall is loaded and configured. + ## Example diff --git a/content/docs/expo/sdk-reference/components/SuperwallLoading.mdx b/content/docs/expo/sdk-reference/components/SuperwallLoading.mdx index 11f81ba..3bbb59a 100644 --- a/content/docs/expo/sdk-reference/components/SuperwallLoading.mdx +++ b/content/docs/expo/sdk-reference/components/SuperwallLoading.mdx @@ -8,6 +8,16 @@ Once Superwall is configured and no longer in a loading state, this component wi **Note:** This component will not render if there's a configuration error. Use `` to handle error states. +## Props + ## Example @@ -54,4 +64,4 @@ function MainAppScreen() { ## Related Components - [``](/expo/sdk-reference/components/SuperwallLoaded) - Renders children when Superwall is ready -- [``](/expo/sdk-reference/components/SuperwallError) - Renders children when Superwall configuration fails \ No newline at end of file +- [``](/expo/sdk-reference/components/SuperwallError) - Renders children when Superwall configuration fails diff --git a/content/docs/expo/sdk-reference/components/SuperwallProvider.mdx b/content/docs/expo/sdk-reference/components/SuperwallProvider.mdx index b4a07f6..f814198 100644 --- a/content/docs/expo/sdk-reference/components/SuperwallProvider.mdx +++ b/content/docs/expo/sdk-reference/components/SuperwallProvider.mdx @@ -5,18 +5,42 @@ title: "SuperwallProvider" `` is the root component for the Superwall SDK. It is used to initialize the SDK with your API key. ## Props + void", + description: "Optional callback invoked when SDK configuration fails.", + }, + children: { + type: "React.ReactNode", + description: "App content to render once configuration succeeds.", + required: true, + }, + }} +/> -### `apiKeys` - -The `apiKeys` prop is an object that contains the API keys for the Superwall SDK. - -### `options` - -Optional configuration options to pass to the native Superwall SDK. See [SuperwallOptions](/expo/guides/configuring) for available options. - -### `onConfigurationError` - -Optional callback invoked when SDK configuration fails. Use this to track errors, show custom UI, or implement retry logic. +### apiKeys + ```tsx ); -} \ No newline at end of file +} diff --git a/content/docs/expo/sdk-reference/getPresentationResult.mdx b/content/docs/expo/sdk-reference/getPresentationResult.mdx index b3a1f78..7cff77c 100644 --- a/content/docs/expo/sdk-reference/getPresentationResult.mdx +++ b/content/docs/expo/sdk-reference/getPresentationResult.mdx @@ -30,21 +30,52 @@ await Superwall.getPresentationResult({ Both variants return a promise that resolves to a `PresentationResult` object from `expo-superwall/compat`. ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `placement` | `string` | Placement to evaluate. Always await `Superwall.configure` before calling. | -| `params` | `Record` or `Map` | Optional parameters that feed audience filters. Keys beginning with `$` are reserved and removed. Nested maps or arrays are not supported. Defaults to omitted. | - + or Map", + description: "Optional parameters that feed audience filters. Keys beginning with `$` are reserved and removed. Nested maps or arrays are not supported.", + default: "omitted", + }, + }} +/> + ## Returns / State The promise resolves to one of the `PresentationResult` subclasses exported from `expo-superwall/compat`: -- `PresentationResultPaywall` – A paywall would be shown. Includes an `experiment` field (`id`, `groupId`, etc.). -- `PresentationResultHoldout` – The user is placed in a holdout for the experiment. -- `PresentationResultNoAudienceMatch` – No matching audience rules. -- `PresentationResultPlacementNotFound` – The placement name is not attached to any campaign. -- `PresentationResultUserIsSubscribed` – The SDK determined the user is already active, so no paywall will show. -- `PresentationResultPaywallNotAvailable` – The paywall could not be displayed (no activity, already showing, offline, etc.). + If configuration fails or the native module throws, the promise rejects—catch and handle these errors as you would any async call. diff --git a/content/docs/expo/sdk-reference/hooks/usePlacement.mdx b/content/docs/expo/sdk-reference/hooks/usePlacement.mdx index dd50e09..7607bd5 100644 --- a/content/docs/expo/sdk-reference/hooks/usePlacement.mdx +++ b/content/docs/expo/sdk-reference/hooks/usePlacement.mdx @@ -19,43 +19,98 @@ function usePlacement( ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `callbacks` | `usePlacementCallbacks?` | Optional callbacks that fire at each stage of the paywall lifecycle. | - + + ### usePlacementCallbacks - -| Name | Type | Description | -|------|------|-------------| -| `onPresent` | `(paywallInfo: PaywallInfo) => void` | Called when a paywall is presented. | -| `onDismiss` | `(paywallInfo: PaywallInfo, result: PaywallResult) => void` | Called when a paywall is dismissed. | -| `onSkip` | `(reason: PaywallSkippedReason) => void` | Called when presentation is skipped (e.g., hold-out, no audience match). | -| `onError` | `(error: string) => void` | Called when the paywall fails to present or another SDK error occurs. | - + void", + description: "Called when a paywall is presented.", + }, + onDismiss: { + type: "(paywallInfo: PaywallInfo, result: PaywallResult) => void", + description: "Called when a paywall is dismissed.", + }, + onSkip: { + type: "(reason: PaywallSkippedReason) => void", + description: "Called when presentation is skipped (e.g., hold-out, no audience match).", + }, + onError: { + type: "(error: string) => void", + description: "Called when the paywall fails to present or another SDK error occurs.", + }, + }} +/> + ### RegisterPlacementArgs - -| Name | Type | Description | -|------|------|-------------| -| `placement` | `string` | Placement name as defined on the Superwall Dashboard. | -| `params` | `Record?` | Optional parameters passed to the placement. | -| `feature` | `() => void` | Function executed **only** if no paywall is shown (the user is allowed through). | - +?", + description: "Optional parameters passed to the placement.", + }, + feature: { + type: "() => void", + description: "Optional function executed **only** if no paywall is shown (the user is allowed through).", + }, + }} +/> -## Returns / State -The hook returns an object with: -• `registerPlacement` — `(args: RegisterPlacementArgs) => Promise` -• `state` — `PaywallState` +## Returns / State + Promise", + description: "Registers a placement and potentially presents a paywall.", + }, + state: { + type: "PaywallState", + description: "Current paywall lifecycle state for this hook instance.", + }, + }} +/> -`PaywallState` union: -- `{ status: "idle" }` -- `{ status: "presented"; paywallInfo: PaywallInfo }` -- `{ status: "dismissed"; result: PaywallResult }` -- `{ status: "skipped"; reason: PaywallSkippedReason }` -- `{ status: "error"; error: string }` +### PaywallState + ## Usage ```tsx @@ -91,4 +146,4 @@ export default function PremiumButton() { } ``` -See also: [Present a paywall](/docs/expo/quickstart/present-first-paywall) \ No newline at end of file +See also: [Present a paywall](/docs/expo/quickstart/present-first-paywall) diff --git a/content/docs/expo/sdk-reference/hooks/useSuperwall.mdx b/content/docs/expo/sdk-reference/hooks/useSuperwall.mdx index 1be70ee..2eb2b2f 100644 --- a/content/docs/expo/sdk-reference/hooks/useSuperwall.mdx +++ b/content/docs/expo/sdk-reference/hooks/useSuperwall.mdx @@ -10,41 +10,155 @@ The `useSuperwall` hook is the core hook that provides access to the Superwall s The hook returns an object representing the Superwall store. If a `selector` function is provided, it returns the selected slice of the store. ### State -- `isConfigured: boolean`: True if the Superwall SDK has been configured with an API key. -- `isLoading: boolean`: True when the SDK is performing an asynchronous operation like configuration. -- `listenersInitialized: boolean`: True if native event listeners have been initialized. -- `user?: UserAttributes | null`: An object containing the current user's attributes. - - `UserAttributes`: - - `aliasId: string`: The alias ID of the user. - - `appUserId: string`: The application-specific user ID. - - `applicationInstalledAt: string`: ISO date string of when the application was installed. - - `seed: number`: A seed value for the user. - - `[key: string]: any`: Other custom user attributes. -- `subscriptionStatus?: SubscriptionStatus`: The current subscription status of the user. - - `SubscriptionStatus` (see `SuperwallExpoModule.types.ts` for full details): - - `status: "UNKNOWN" | "INACTIVE" | "ACTIVE"` - - `entitlements?: Entitlement[]` (if status is "ACTIVE") - - `Entitlement`: `{ id: string, type: EntitlementType }` + ### Actions (Functions) -- `configure: (apiKey: string, options?: Record) => Promise`: Initializes the Superwall SDK with the provided API key and optional configuration options. - - **Android / Google Play**: Include `{ passIdentifiersToPlayStore: true }` in the `options` object if you need the raw `appUserId` forwarded to Google Play as `obfuscatedExternalAccountId`. Otherwise the SDK sends a SHA-256 hash. This flag is ignored on iOS builds but you can guard it with `Platform.OS === "android"` if you prefer. -- `identify: (userId: string, options?: IdentifyOptions) => Promise`: Identifies the user with the given `userId`. - - `IdentifyOptions`: - - `restorePaywallAssignments?: boolean`: If true, restores paywall assignments from a previous session. -- `reset: () => Promise`: Resets the user's identity and clears any stored user-specific data. This is equivalent to logging out the user. -- `registerPlacement: (placement: string, params?: Record, handlerId?: string | null) => Promise`: Registers a placement. This may or may not present a paywall depending on campaign rules. `handlerId` is used internally by `usePlacement` to associate events. -- `getPresentationResult: (placement: string, params?: Record) => Promise`: Gets the presentation result for a given placement. -- `dismiss: () => Promise`: Dismisses any currently presented paywall. -- `preloadAllPaywalls: () => Promise`: Preloads all paywalls. -- `preloadPaywalls: (placements: string[]) => Promise`: Preloads paywalls for the specified placement IDs. -- `setUserAttributes: (attrs: Record) => Promise`: Sets custom attributes for the current user. -- `getUserAttributes: () => Promise>`: Retrieves the attributes of the current user. -- `setLogLevel: (level: string) => Promise`: Sets the log level for the SDK. `level` can be one of: `"debug"`, `"info"`, `"warn"`, `"error"`, `"none"`. + Promise", + description: "Initializes the SDK with your API key and optional configuration. Android only: set options.passIdentifiersToPlayStore to send raw appUserId to Google Play.", + }, + identify: { + type: "(userId: string, options?: IdentifyOptions) => Promise", + description: "Identifies the user with the given userId.", + }, + reset: { + type: "() => Promise", + description: "Resets the user's identity and clears user-specific data.", + }, + registerPlacement: { + type: "(placement: string, params?: Record, handlerId?: string | null) => Promise", + description: "Registers a placement and optionally triggers a paywall.", + }, + getPresentationResult: { + type: "(placement: string, params?: Record) => Promise", + description: "Gets the presentation result for a placement without presenting.", + }, + dismiss: { + type: "() => Promise", + description: "Dismisses any currently presented paywall.", + }, + preloadAllPaywalls: { + type: "() => Promise", + description: "Preloads all paywalls configured in the dashboard.", + }, + preloadPaywalls: { + type: "(placements: string[]) => Promise", + description: "Preloads paywalls for the specified placements.", + }, + setUserAttributes: { + type: "(attrs: Record) => Promise", + description: "Sets custom attributes for the current user.", + }, + getUserAttributes: { + type: "() => Promise>", + description: "Retrieves the current user's attributes.", + }, + setLogLevel: { + type: "(level: string) => Promise", + description: "Sets the SDK log level (debug, info, warn, error, none).", + }, + setIntegrationAttributes: { + type: "(attributes: IntegrationAttributes) => Promise", + description: "Sets third-party integration identifiers.", + }, + getIntegrationAttributes: { + type: "() => Promise>", + description: "Returns currently set integration attributes.", + }, + setSubscriptionStatus: { + type: "(status: SubscriptionStatus) => Promise", + description: "Manually sets the user's subscription status.", + }, + getDeviceAttributes: { + type: "() => Promise>", + description: "Returns device attributes from the native SDK.", + }, + getEntitlements: { + type: "() => Promise", + description: "Fetches active and inactive entitlements for the user.", + }, + }} +/> ### Selector (Optional Parameter) - -- `selector?: (state: SuperwallStore) => T`: A function that receives the entire `SuperwallStore` state and returns a selected part of it. Useful for performance optimization by only re-rendering components when the selected state changes. Uses `zustand/shallow` for shallow equality checking. + T", + description: "Selects a slice of store state for shallow-equality rendering.", + }, + }} +/> + +### UserAttributes + + +### SubscriptionStatus + ## Example (Direct Usage - Advanced) @@ -81,4 +195,4 @@ async function configureSuperwall() { : undefined, }) } -``` \ No newline at end of file +``` diff --git a/content/docs/expo/sdk-reference/hooks/useSuperwallEvents.mdx b/content/docs/expo/sdk-reference/hooks/useSuperwallEvents.mdx index 84976fa..598e882 100644 --- a/content/docs/expo/sdk-reference/hooks/useSuperwallEvents.mdx +++ b/content/docs/expo/sdk-reference/hooks/useSuperwallEvents.mdx @@ -7,24 +7,96 @@ The `useSuperwallEvents` hook provides a low-level way to subscribe to *any* nat ## Parameters -- `callbacks?: SuperwallEventCallbacks`: An object where keys are event names and values are the corresponding callback functions. - - `SuperwallEventCallbacks`: (Refer to `src/useSuperwallEvents.ts` and `src/SuperwallExpoModule.types.ts` for a full list of subscribable events and their payload types. Common events include): - - `onPaywallPresent?: (info: PaywallInfo) => void` - - `onPaywallDismiss?: (info: PaywallInfo, result: PaywallResult) => void` - - `onPaywallSkip?: (reason: PaywallSkippedReason) => void` - - `onPaywallError?: (error: string) => void` - - `onSubscriptionStatusChange?: (status: SubscriptionStatus) => void` - - `onSuperwallEvent?: (eventInfo: SuperwallEventInfo) => void`: For generic Superwall events. - - `SuperwallEventInfo`: `{ event: SuperwallEvent, params: Record }` - - `onCustomPaywallAction?: (name: string) => void`: When a custom action is triggered from a paywall. - - `willDismissPaywall?: (info: PaywallInfo) => void` - - `willPresentPaywall?: (info: PaywallInfo) => void` - - `didDismissPaywall?: (info: PaywallInfo) => void` - - `didPresentPaywall?: (info: PaywallInfo) => void` - - `onPaywallWillOpenURL?: (url: string) => void` - - `onPaywallWillOpenDeepLink?: (url: string) => void` - - `onLog?: (params: { level: LogLevel, scope: LogScope, message: string | null, info: Record | null, error: string | null }) => void` - - `handlerId?: string`: (Optional) If provided, some events like `onPaywallPresent`, `onPaywallDismiss`, `onPaywallSkip` will only be triggered if the event originated from a `registerPlacement` call associated with the same `handlerId`. This is used internally by `usePlacement`. + + +### SuperwallEventCallbacks + void", + description: "Called when a paywall is presented.", + }, + onPaywallDismiss: { + type: "(paywallInfo: PaywallInfo, result: PaywallResult) => void", + description: "Called when a paywall is dismissed.", + }, + onPaywallSkip: { + type: "(reason: PaywallSkippedReason) => void", + description: "Called when a paywall is skipped.", + }, + onPaywallError: { + type: "(error: string) => void", + description: "Called when paywall presentation fails or another SDK error occurs.", + }, + onSubscriptionStatusChange: { + type: "(status: SubscriptionStatus) => void", + description: "Called when the user's subscription status changes.", + }, + onSuperwallEvent: { + type: "(eventInfo: SuperwallEventInfo) => void", + description: "Called for generic Superwall events with payload metadata.", + }, + onCustomPaywallAction: { + type: "(name: string) => void", + description: "Called when a custom action is triggered from a paywall.", + }, + willDismissPaywall: { + type: "(paywallInfo: PaywallInfo) => void", + description: "Called just before a paywall is dismissed.", + }, + willPresentPaywall: { + type: "(paywallInfo: PaywallInfo) => void", + description: "Called just before a paywall is presented.", + }, + didDismissPaywall: { + type: "(paywallInfo: PaywallInfo) => void", + description: "Called after a paywall has been dismissed.", + }, + didPresentPaywall: { + type: "(paywallInfo: PaywallInfo) => void", + description: "Called after a paywall has been presented.", + }, + onPaywallWillOpenURL: { + type: "(url: string) => void", + description: "Called when the paywall attempts to open a URL.", + }, + onPaywallWillOpenDeepLink: { + type: "(url: string) => void", + description: "Called when the paywall attempts to open a deep link.", + }, + onLog: { + type: "(params: { level: LogLevel; scope: LogScope; message: string | null; info: Record | null; error: string | null }) => void", + description: "Called for log messages emitted by the SDK.", + }, + willRedeemLink: { + type: "() => void", + description: "Called before the SDK attempts to redeem a promotional link.", + }, + didRedeemLink: { + type: "(result: RedemptionResult) => void", + description: "Called after the SDK attempts to redeem a promotional link.", + }, + onPurchase: { + type: "(params: OnPurchaseParams) => void", + description: "Called when a purchase is initiated. Params differ by platform.", + }, + onPurchaseRestore: { + type: "() => void", + description: "Called when a purchase restore is initiated.", + }, + handlerId: { + type: "string?", + description: "Optional scope for paywall events from a specific registerPlacement handler.", + }, + }} +/> ## Returned Values @@ -49,4 +121,4 @@ function EventLogger() { }); } -``` \ No newline at end of file +``` diff --git a/content/docs/expo/sdk-reference/hooks/useUser.mdx b/content/docs/expo/sdk-reference/hooks/useUser.mdx index 4c983c9..d1508eb 100644 --- a/content/docs/expo/sdk-reference/hooks/useUser.mdx +++ b/content/docs/expo/sdk-reference/hooks/useUser.mdx @@ -7,26 +7,102 @@ The `useUser` hook provides a convenient way to manage user identity and attribu ## Returned Values -An object containing: + Promise", + description: "Identifies the user with Superwall.", + }, + update: { + type: "(attributes: Record | ((old: Record) => Record)) => Promise", + description: "Updates the current user's attributes.", + }, + signOut: { + type: "() => void", + description: "Resets the user's identity and clears user-specific data.", + }, + refresh: { + type: "() => Promise>", + description: "Refreshes user attributes and subscription status from Superwall.", + }, + setIntegrationAttributes: { + type: "(attributes: IntegrationAttributes) => Promise", + description: "Sets third-party integration identifiers (Adjust, Amplitude, AppsFlyer, etc.).", + }, + getIntegrationAttributes: { + type: "() => Promise>", + description: "Returns the currently set integration attributes.", + }, + getEntitlements: { + type: "() => Promise", + description: "Fetches active and inactive entitlements for the user.", + }, + setSubscriptionStatus: { + type: "(status: SubscriptionStatus) => Promise", + description: "Manually sets the user's subscription status.", + }, + subscriptionStatus: { + type: "SubscriptionStatus", + description: "Current subscription status for the user.", + }, + user: { + type: "UserAttributes | null", + description: "Current user attributes. Null after signOut; undefined before initial load.", + }, + }} +/> -- `identify: (userId: string, options?: IdentifyOptions) => Promise`: Identifies the user with Superwall. - - `userId: string`: The unique identifier for the user. - - `options?: IdentifyOptions`: Optional parameters for identification. - - `restorePaywallAssignments?: boolean`: If true, attempts to restore paywall assignments for this user. -- `update: (attributes: Record | ((old: Record) => Record)) => Promise`: Updates the current user's attributes. - - `attributes`: Either an object of attributes to set/update, or a function that takes the old attributes and returns the new attributes. -- `signOut: () => void`: Resets the user's identity, effectively signing them out from Superwall's perspective. -- `refresh: () => Promise>`: Manually refreshes the user's attributes and subscription status from the Superwall servers. Returns the refreshed user attributes. -- `setIntegrationAttributes: (attributes: IntegrationAttributes) => Promise`: Sets attributes for third-party integrations (e.g., attribution providers, analytics platforms). Used to link user IDs from services like Adjust, Amplitude, AppsFlyer, etc. -- `getIntegrationAttributes: () => Promise>`: Gets the currently set integration attributes. -- `getEntitlements: () => Promise`: Retrieves the user's entitlements from Superwall's servers. Returns both active and inactive entitlements. -- `setSubscriptionStatus: (status: SubscriptionStatus) => Promise`: Manually sets the subscription status for the user. Useful when integrating with RevenueCat or other subscription providers. -- `subscriptionStatus?: SubscriptionStatus`: The current subscription status of the user. - - `SubscriptionStatus`: (As defined in `useSuperwall` and `SuperwallExpoModule.types.ts`) - - `status: "UNKNOWN" | "INACTIVE" | "ACTIVE"` - - `entitlements?: Entitlement[]` (if status is "ACTIVE") -- `user?: UserAttributes | null`: An object containing the current user's attributes (e.g., `appUserId`, `aliasId`, custom attributes). - - `UserAttributes`: (As defined in `useSuperwall`) +### IdentifyOptions + + +### SubscriptionStatus + + +### UserAttributes + ## Usage -[Managing Users](/expo/guides/managing-users) \ No newline at end of file +[Managing Users](/expo/guides/managing-users) diff --git a/content/docs/flutter/guides/testing-purchases.mdx b/content/docs/flutter/guides/testing-purchases.mdx index e931695..00807cc 100644 --- a/content/docs/flutter/guides/testing-purchases.mdx +++ b/content/docs/flutter/guides/testing-purchases.mdx @@ -3,4 +3,6 @@ title: "StoreKit testing (iOS only)" description: "How to set up StoreKit testing for iOS when using the Flutter SDK." --- +StoreKit testing in Xcode is a local test environment for testing in-app purchases without requiring a connection to App Store servers. Set up in-app purchases in a local StoreKit configuration file in your Xcode project, or create a synced StoreKit configuration file in Xcode from your in-app purchase settings in App Store Connect. After you enable the configuration file, the test environment uses this local data on your paywalls when your app calls StoreKit APIs. + ../../../shared/testing-purchases.mdx diff --git a/content/docs/flutter/sdk-reference/CustomerInfo.mdx b/content/docs/flutter/sdk-reference/CustomerInfo.mdx index 1d97ae2..dcf7e29 100644 --- a/content/docs/flutter/sdk-reference/CustomerInfo.mdx +++ b/content/docs/flutter/sdk-reference/CustomerInfo.mdx @@ -18,14 +18,31 @@ class CustomerInfo { ## Properties - -| Property | Type | Description | -|----------|------|-------------| -| `subscriptions` | `List` | All subscription transactions the user has made. | -| `nonSubscriptions` | `List` | All non-subscription transactions (consumables and non-consumables) the user has made. | -| `entitlements` | `List` | All entitlements available to the user. | -| `userId` | `String` | The ID of the user. | - +", + description: "All subscription transactions the user has made.", + required: true, + }, + nonSubscriptions: { + type: "List", + description: "All non-subscription transactions (consumables and non-consumables) the user has made.", + required: true, + }, + entitlements: { + type: "List", + description: "All entitlements available to the user.", + required: true, + }, + userId: { + type: "String", + description: "The ID of the user.", + required: true, + }, + }} +/> + ## Usage diff --git a/content/docs/flutter/sdk-reference/Entitlements.mdx b/content/docs/flutter/sdk-reference/Entitlements.mdx index 3638906..ba275e8 100644 --- a/content/docs/flutter/sdk-reference/Entitlements.mdx +++ b/content/docs/flutter/sdk-reference/Entitlements.mdx @@ -20,14 +20,31 @@ class Entitlements { ## Properties - -| Property | Type | Description | -|----------|------|-------------| -| `active` | `Set` | All active entitlements available to the user. | -| `inactive` | `Set` | All inactive entitlements. | -| `all` | `Set` | All entitlements (both active and inactive). | -| `web` | `Set` | Entitlements from web checkout. | - +", + description: "All active entitlements available to the user.", + required: true, + }, + inactive: { + type: "Set", + description: "All inactive entitlements.", + required: true, + }, + all: { + type: "Set", + description: "All entitlements (both active and inactive).", + required: true, + }, + web: { + type: "Set", + description: "Entitlements from web checkout.", + required: true, + }, + }} +/> + ## Methods diff --git a/content/docs/flutter/sdk-reference/IntegrationAttribute.mdx b/content/docs/flutter/sdk-reference/IntegrationAttribute.mdx index 21bce6f..117e083 100644 --- a/content/docs/flutter/sdk-reference/IntegrationAttribute.mdx +++ b/content/docs/flutter/sdk-reference/IntegrationAttribute.mdx @@ -34,30 +34,11 @@ enum IntegrationAttribute { ## Values - -| Value | Description | -|-------|-------------| -| `adjustId` | The unique Adjust identifier for the user. | -| `amplitudeDeviceId` | The Amplitude device identifier. | -| `amplitudeUserId` | The Amplitude user identifier. | -| `appsflyerId` | The unique Appsflyer identifier for the user. | -| `brazeAliasName` | The Braze `alias_name` in User Alias Object. | -| `brazeAliasLabel` | The Braze `alias_label` in User Alias Object. | -| `onesignalId` | The OneSignal Player identifier for the user. | -| `fbAnonId` | The Facebook Anonymous identifier for the user. | -| `firebaseAppInstanceId` | The Firebase instance identifier. | -| `iterableUserId` | The Iterable identifier for the user. | -| `iterableCampaignId` | The Iterable campaign identifier. | -| `iterableTemplateId` | The Iterable template identifier. | -| `mixpanelDistinctId` | The Mixpanel user identifier. | -| `mparticleId` | The unique mParticle user identifier (mpid). | -| `clevertapId` | The CleverTap user identifier. | -| `airshipChannelId` | The Airship channel identifier for the user. | -| `kochavaDeviceId` | The unique Kochava device identifier. | -| `tenjinId` | The Tenjin identifier. | -| `posthogUserId` | The PostHog User identifier. | -| `customerioId` | The Customer.io person's identifier (`id`). | - + + ## Usage diff --git a/content/docs/flutter/sdk-reference/PaywallOptions.mdx b/content/docs/flutter/sdk-reference/PaywallOptions.mdx index 34cab30..ae3b480 100644 --- a/content/docs/flutter/sdk-reference/PaywallOptions.mdx +++ b/content/docs/flutter/sdk-reference/PaywallOptions.mdx @@ -35,23 +35,74 @@ enum TransactionBackgroundView { spinner, none } ``` ## Parameters - -| Property | Type | Description | -|----------|------|-------------| -| `isHapticFeedbackEnabled` | `bool` | Enables haptic feedback during key paywall interactions. Defaults to `true`. | -| `restoreFailed` | `RestoreFailed` | Messaging for the restore-failed alert. | -| `restoreFailed.title` | `String` | Title for restore-failed alert. Defaults to `"No Subscription Found"`. | -| `restoreFailed.message` | `String` | Message for restore-failed alert. Defaults to `"We couldn't find an active subscription for your account."` | -| `restoreFailed.closeButtonTitle` | `String` | Close button title for restore-failed alert. Defaults to `"Okay"`. | -| `shouldShowPurchaseFailureAlert` | `bool` | Shows an alert after a purchase fails. Set to `false` if you handle failures via a `PurchaseController`. Defaults to `true`. | -| `shouldPreload` | `bool` | Preloads and caches trigger paywalls and products during SDK initialization. Defaults to `true`. | -| `automaticallyDismiss` | `bool` | Automatically dismisses the paywall on successful purchase or restore. Defaults to `true`. | -| `transactionBackgroundView` | `TransactionBackgroundView` | View shown behind the system payment sheet during a transaction. Defaults to `.spinner`. | -| `shouldShowWebRestorationAlert` | `bool` | Shows an alert asking the user to try restoring on the web if web checkout is enabled. Defaults to `true`. | -| `overrideProductsByName` | `Map?` | Overrides products on all paywalls using name→identifier mapping (e.g., `"primary"` → `"com.example.premium_monthly"`). | -| `shouldShowWebPurchaseConfirmationAlert` | `bool` | Shows a localized alert confirming a successful web checkout purchase. Defaults to `true`. | -| `onBackPressed` | `void Function(PaywallInfo?)?` | Android only. Invoked when the system back button is pressed while a paywall is visible. Call `Superwall.shared.dismiss()` inside if you want to close the paywall. | - +?", + description: "Overrides products on all paywalls using name\u2192identifier mapping (e.g., `\"primary\"` \u2192 `\"com.example.premium_monthly\"`).", + }, + shouldShowWebPurchaseConfirmationAlert: { + type: "bool", + description: "Shows a localized alert confirming a successful web checkout purchase.", + default: "true", + }, + onBackPressed: { + type: "void Function(PaywallInfo?)?", + description: "Android only. Invoked when the system back button is pressed while a paywall is visible. Call `Superwall.shared.dismiss()` inside if you want to close the paywall.", + }, + }} +/> + ## Usage ```dart diff --git a/content/docs/flutter/sdk-reference/PaywallPresentationHandler.mdx b/content/docs/flutter/sdk-reference/PaywallPresentationHandler.mdx index baca9fb..3c433b4 100644 --- a/content/docs/flutter/sdk-reference/PaywallPresentationHandler.mdx +++ b/content/docs/flutter/sdk-reference/PaywallPresentationHandler.mdx @@ -25,14 +25,31 @@ class PaywallPresentationHandler { ``` ## Parameters - -| Method | Parameters | Description | -|--------|------------|-------------| -| `onPresent` | `handler: (PaywallInfo) -> void` | Sets a handler called when the paywall is presented. | -| `onDismiss` | `handler: (PaywallInfo, PaywallResult) -> void` | Sets a handler called when the paywall is dismissed. | -| `onSkip` | `handler: (PaywallSkippedReason) -> void` | Sets a handler called when paywall presentation is skipped. | -| `onError` | `handler: (String) -> void` | Sets a handler called when an error occurs during presentation. | - + void", + description: "Sets a handler called when the paywall is presented.", + required: true, + }, + onDismiss: { + type: "handler: (PaywallInfo, PaywallResult) -> void", + description: "Sets a handler called when the paywall is dismissed.", + required: true, + }, + onSkip: { + type: "handler: (PaywallSkippedReason) -> void", + description: "Sets a handler called when paywall presentation is skipped.", + required: true, + }, + onError: { + type: "handler: (String) -> void", + description: "Sets a handler called when an error occurs during presentation.", + required: true, + }, + }} +/> + ## Returns / State Each method returns `void` and configures the handler for the specific paywall lifecycle event. diff --git a/content/docs/flutter/sdk-reference/PresentationResult.mdx b/content/docs/flutter/sdk-reference/PresentationResult.mdx index 06761ae..cf020cd 100644 --- a/content/docs/flutter/sdk-reference/PresentationResult.mdx +++ b/content/docs/flutter/sdk-reference/PresentationResult.mdx @@ -29,15 +29,11 @@ class PaywallNotAvailablePresentationResult extends PresentationResult; ## Cases - -| Case | Description | -|------|-------------| -| `PlacementNotFoundPresentationResult` | The placement was not found or doesn't exist. | -| `NoAudienceMatchPresentationResult` | No audience match for the placement based on the current user attributes. | -| `PaywallPresentationResult` | A paywall would be or was presented. Contains experiment information. | -| `HoldoutPresentationResult` | The user is in a holdout group. Contains experiment information. | -| `PaywallNotAvailablePresentationResult` | The paywall is not available (e.g., network error, configuration issue). | - + + ## Experiment Information diff --git a/content/docs/flutter/sdk-reference/PurchaseController.mdx b/content/docs/flutter/sdk-reference/PurchaseController.mdx index a82a49d..2358f48 100644 --- a/content/docs/flutter/sdk-reference/PurchaseController.mdx +++ b/content/docs/flutter/sdk-reference/PurchaseController.mdx @@ -28,13 +28,26 @@ abstract class PurchaseController { ``` ## Parameters - -| Method | Parameters | Description | -|--------|------------|-------------| -| `purchaseFromAppStore` | `productId: String` | Handles iOS App Store purchases. | -| `purchaseFromGooglePlay` | `productId: String`, `basePlanId: String?`, `offerId: String?` | Handles Google Play Store purchases with optional base plan and offer. | -| `restorePurchases` | None | Restores previous purchases across platforms. | - + + ## Returns / State - `purchaseFromAppStore` and `purchaseFromGooglePlay` return `Future` @@ -46,4 +59,4 @@ For most use cases, use RevenueCat integration instead: See the [Using RevenueCat guide](/flutter/guides/using-revenuecat) for complete setup instructions. -Custom implementation is only needed for advanced use cases where you have your own purchase handling system. \ No newline at end of file +Custom implementation is only needed for advanced use cases where you have your own purchase handling system. diff --git a/content/docs/flutter/sdk-reference/SuperwallOptions.mdx b/content/docs/flutter/sdk-reference/SuperwallOptions.mdx index 4771f29..c5514f3 100644 --- a/content/docs/flutter/sdk-reference/SuperwallOptions.mdx +++ b/content/docs/flutter/sdk-reference/SuperwallOptions.mdx @@ -20,17 +20,47 @@ class SuperwallOptions { ``` ## Parameters - -| Property | Type | Description | -|----------|------|-------------| -| `paywalls` | [`PaywallOptions`](/flutter/sdk-reference/PaywallOptions) | Configuration for paywall presentation behavior. | -| `networkEnvironment` | `NetworkEnvironment` | Network environment for API calls (release/releaseCandidate/developer). Only change when instructed by Superwall. | -| `isExternalDataCollectionEnabled` | `bool` | Enables external analytics collection. Defaults to `true`. | -| `localeIdentifier` | `String?` | Override locale for paywall localization. Defaults to device locale. | -| `isGameControllerEnabled` | `bool` | Enables game controller support. Defaults to `false`. | -| `logging` | `Logging` | Configuration for SDK logging levels and behavior. | -| `passIdentifiersToPlayStore` | `bool` | When `true`, Android builds send the plain `appUserId` to Google Play as `obfuscatedExternalAccountId`. Defaults to `false`. | - + + ## Android-only: `passIdentifiersToPlayStore` Flutter apps can target both iOS and Android. Google Play always consumes the identifier you send through `BillingFlowParams.Builder.setObfuscatedAccountId`, which the SDK sources from `Superwall.instance.externalAccountId`. diff --git a/content/docs/flutter/sdk-reference/advanced/setSubscriptionStatus.mdx b/content/docs/flutter/sdk-reference/advanced/setSubscriptionStatus.mdx index 636dffd..8d37a4c 100644 --- a/content/docs/flutter/sdk-reference/advanced/setSubscriptionStatus.mdx +++ b/content/docs/flutter/sdk-reference/advanced/setSubscriptionStatus.mdx @@ -16,11 +16,16 @@ Future setSubscriptionStatus(SubscriptionStatus status) ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `status` | `SubscriptionStatus` | The subscription status to set (active, inactive, unknown). | - + + ## Returns / State Returns a `Future` that completes when the subscription status is updated. diff --git a/content/docs/flutter/sdk-reference/configure.mdx b/content/docs/flutter/sdk-reference/configure.mdx index 75d1687..e5d22e6 100644 --- a/content/docs/flutter/sdk-reference/configure.mdx +++ b/content/docs/flutter/sdk-reference/configure.mdx @@ -44,14 +44,30 @@ fun configure( ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `apiKey` | `String` | Your Superwall API key from the dashboard. | -| `purchaseController` | `PurchaseController?` | Optional custom purchase controller. Defaults to `null` to use the default controller. | -| `options` | `SuperwallOptions?` | Optional configuration options. Defaults to `null` for default settings. | -| `completion` | `Function?` | Optional callback called when configuration completes. | - + + ## Returns / State Returns a `Superwall` instance that is immediately configured and ready to use. diff --git a/content/docs/flutter/sdk-reference/consume.mdx b/content/docs/flutter/sdk-reference/consume.mdx index b520b37..5ce0619 100644 --- a/content/docs/flutter/sdk-reference/consume.mdx +++ b/content/docs/flutter/sdk-reference/consume.mdx @@ -12,11 +12,16 @@ Future consume(String purchaseToken) ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `purchaseToken` | `String` | The purchase token of the in-app purchase to consume. | - + + ## Returns / State Returns a `Future` that resolves to the purchase token of the consumed purchase. diff --git a/content/docs/flutter/sdk-reference/getPresentationResult.mdx b/content/docs/flutter/sdk-reference/getPresentationResult.mdx index e0564d8..9c71c38 100644 --- a/content/docs/flutter/sdk-reference/getPresentationResult.mdx +++ b/content/docs/flutter/sdk-reference/getPresentationResult.mdx @@ -15,12 +15,21 @@ Future getPresentationResult( ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `placement` | `String` | The name of the placement to check. | -| `params` | `Map?` | Optional parameters to pass with the placement. These can be referenced within campaign rules. Keys beginning with `$` are reserved for Superwall and will be dropped. Nested maps and lists are currently unsupported and will be ignored. Defaults to `null`. | - +?", + description: "Optional parameters to pass with the placement. These can be referenced within campaign rules. Keys beginning with `$` are reserved for Superwall and will be dropped. Nested maps and lists are currently unsupported and will be ignored.", + default: "null", + }, + }} +/> + ## Returns / State Returns a `Future` that resolves to one of the following: diff --git a/content/docs/flutter/sdk-reference/handleDeepLink.mdx b/content/docs/flutter/sdk-reference/handleDeepLink.mdx index 0845f42..8fd99e8 100644 --- a/content/docs/flutter/sdk-reference/handleDeepLink.mdx +++ b/content/docs/flutter/sdk-reference/handleDeepLink.mdx @@ -16,11 +16,16 @@ Future handleDeepLink(Uri url) ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `url` | `Uri` | The deep link URL to process. | - + + ## Returns / State Returns a `Future` indicating whether the URL was handled by Superwall (`true`) or should be processed by your app (`false`). diff --git a/content/docs/flutter/sdk-reference/identify.mdx b/content/docs/flutter/sdk-reference/identify.mdx index ee3b371..35513ae 100644 --- a/content/docs/flutter/sdk-reference/identify.mdx +++ b/content/docs/flutter/sdk-reference/identify.mdx @@ -16,12 +16,20 @@ Future identify(String userId, [IdentityOptions? options]) ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `userId` | `String` | A unique identifier for the user. | -| `options` | `IdentityOptions?` | Optional configuration for identity behavior. | - + + ## Returns / State Returns a `Future` that completes when the user identification is finished. diff --git a/content/docs/flutter/sdk-reference/overrideProductsByName.mdx b/content/docs/flutter/sdk-reference/overrideProductsByName.mdx index d5a6e22..85f0334 100644 --- a/content/docs/flutter/sdk-reference/overrideProductsByName.mdx +++ b/content/docs/flutter/sdk-reference/overrideProductsByName.mdx @@ -16,11 +16,15 @@ Future?> getOverrideProductsByName() ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `overrideProducts` | `Map?` | A map where keys are product names in paywalls (e.g., `"primary"`, `"secondary"`) and values are the product identifiers to replace them with (e.g., `"com.example.premium_monthly"`). Pass `null` to clear all overrides. | - +?", + description: "A map where keys are product names in paywalls (e.g., `\"primary\"`, `\"secondary\"`) and values are the product identifiers to replace them with (e.g., `\"com.example.premium_monthly\"`). Pass `null` to clear all overrides.", + }, + }} +/> + ## Returns / State - **Setter**: Sets the global product overrides. Changes take effect immediately for all future paywall presentations. diff --git a/content/docs/flutter/sdk-reference/register.mdx b/content/docs/flutter/sdk-reference/register.mdx index 1b11641..c968f18 100644 --- a/content/docs/flutter/sdk-reference/register.mdx +++ b/content/docs/flutter/sdk-reference/register.mdx @@ -25,14 +25,30 @@ Future registerPlacement( ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `placement` | `String` | The name of the placement you wish to register. | -| `params` | `Map?` | Optional parameters to pass with your placement. These can be referenced within campaign rules. Keys beginning with `$` are reserved for Superwall and will be dropped. Nested maps and lists are currently unsupported and will be ignored. Defaults to `null`. | -| `handler` | `PaywallPresentationHandler?` | A handler whose functions provide status updates for the paywall lifecycle. Defaults to `null`. | -| `feature` | `Function()?` | A callback representing the gated feature. It is executed based on the paywall's gating mode: called immediately for **Non-Gated**, called after the user subscribes or if already subscribed for **Gated**. | - +?", + description: "Optional parameters to pass with your placement. These can be referenced within campaign rules. Keys beginning with `$` are reserved for Superwall and will be dropped. Nested maps and lists are currently unsupported and will be ignored.", + default: "null", + }, + handler: { + type: "PaywallPresentationHandler?", + description: "A handler whose functions provide status updates for the paywall lifecycle.", + default: "null", + }, + feature: { + type: "Function()?", + description: "A callback representing the gated feature. It is executed based on the paywall's gating mode: called immediately for **Non-Gated**, called after the user subscribes or if already subscribed for **Gated**.", + }, + }} +/> + ## Returns / State This function returns a `Future`. If you supply a `feature` callback, it will be executed according to the paywall's gating configuration, as described above. diff --git a/content/docs/flutter/sdk-reference/setIntegrationAttribute.mdx b/content/docs/flutter/sdk-reference/setIntegrationAttribute.mdx index f5ae99a..1276876 100644 --- a/content/docs/flutter/sdk-reference/setIntegrationAttribute.mdx +++ b/content/docs/flutter/sdk-reference/setIntegrationAttribute.mdx @@ -15,12 +15,20 @@ Future setIntegrationAttribute( ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `attribute` | `IntegrationAttribute` | The integration attribute key specifying the integration provider. | -| `value` | `String?` | The value to associate with the attribute. Pass `null` to remove the attribute. | - + + ## Returns / State Returns a `Future` that completes when the attribute is set. diff --git a/content/docs/flutter/sdk-reference/setIntegrationAttributes.mdx b/content/docs/flutter/sdk-reference/setIntegrationAttributes.mdx index 4778745..ec67146 100644 --- a/content/docs/flutter/sdk-reference/setIntegrationAttributes.mdx +++ b/content/docs/flutter/sdk-reference/setIntegrationAttributes.mdx @@ -14,11 +14,16 @@ Future setIntegrationAttributes( ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `attributes` | `Map` | A map of integration attribute keys to their values. Pass `null` as a value to remove that attribute. | - +", + description: "A map of integration attribute keys to their values. Pass `null` as a value to remove that attribute.", + required: true, + }, + }} +/> + ## Returns / State Returns a `Future` that completes when all attributes are set. diff --git a/content/docs/flutter/sdk-reference/setUserAttributes.mdx b/content/docs/flutter/sdk-reference/setUserAttributes.mdx index 3337b22..c5e483e 100644 --- a/content/docs/flutter/sdk-reference/setUserAttributes.mdx +++ b/content/docs/flutter/sdk-reference/setUserAttributes.mdx @@ -16,11 +16,16 @@ Future setUserAttributes(Map userAttributes) ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `userAttributes` | `Map` | A map of user attributes to set. Values can be strings, numbers, booleans, or dates. | - +", + description: "A map of user attributes to set. Values can be strings, numbers, booleans, or dates.", + required: true, + }, + }} +/> + ## Returns / State Returns a `Future` that completes when the user attributes are set. diff --git a/content/docs/ios/guides/testing-purchases.mdx b/content/docs/ios/guides/testing-purchases.mdx index 27be9d6..964ff77 100644 --- a/content/docs/ios/guides/testing-purchases.mdx +++ b/content/docs/ios/guides/testing-purchases.mdx @@ -2,4 +2,6 @@ title: "Setting up StoreKit testing" --- +StoreKit testing in Xcode is a local test environment for testing in-app purchases without requiring a connection to App Store servers. Set up in-app purchases in a local StoreKit configuration file in your Xcode project, or create a synced StoreKit configuration file in Xcode from your in-app purchase settings in App Store Connect. After you enable the configuration file, the test environment uses this local data on your paywalls when your app calls StoreKit APIs. + ../../../shared/testing-purchases.mdx diff --git a/content/docs/ios/sdk-reference/NonSubscriptionTransaction.mdx b/content/docs/ios/sdk-reference/NonSubscriptionTransaction.mdx index 795ddb4..28380ce 100644 --- a/content/docs/ios/sdk-reference/NonSubscriptionTransaction.mdx +++ b/content/docs/ios/sdk-reference/NonSubscriptionTransaction.mdx @@ -12,16 +12,41 @@ Provides details about one-time purchases in [`CustomerInfo`](/ios/sdk-reference ## Properties - -| Property | Type | Description | -|----------|------|-------------| -| `transactionId` | `String` | Unique identifier for the transaction. | -| `productId` | `String` | Product identifier for the purchase. | -| `purchaseDate` | `Date` | When the charge occurred. | -| `isConsumable` | `Bool` | `true` for consumables, `false` for non-consumables. | -| `isRevoked` | `Bool` | `true` if the transaction has been revoked. | -| `store` | `ProductStore` | Store that fulfilled the purchase (4.11.0+). | - + + ### Store values (4.11.0+) `appStore`, `stripe`, `paddle`, `playStore`, `superwall`, `other`. diff --git a/content/docs/ios/sdk-reference/PaywallOptions.mdx b/content/docs/ios/sdk-reference/PaywallOptions.mdx index 64c1c4e..59e6da8 100644 --- a/content/docs/ios/sdk-reference/PaywallOptions.mdx +++ b/content/docs/ios/sdk-reference/PaywallOptions.mdx @@ -49,27 +49,93 @@ public final class PaywallOptions: NSObject { ``` ## Parameters - -| Property | Type | Description | -|----------|------|-------------| -| `isHapticFeedbackEnabled` | `Bool` | Enables haptic feedback during key paywall interactions. Defaults to `true`. | -| `restoreFailed` | `RestoreFailed` | Messaging for the restore-failed alert. | -| `restoreFailed.title` | `String` | Title for restore-failed alert. Defaults to "No Subscription Found". | -| `restoreFailed.message` | `String` | Message for restore-failed alert. Defaults to "We couldn't find an active subscription for your account." | -| `restoreFailed.closeButtonTitle` | `String` | Close button title for restore-failed alert. Defaults to "Okay". | -| `shouldShowWebRestorationAlert` | `Bool` | Shows an alert asking the user to try restoring on the web if web checkout is enabled. Defaults to `true`. | -| `notificationPermissionsDenied` | `NotificationPermissionsDenied?` | Customize the alert shown when notification permissions are denied. `nil` disables the alert. | -| `notificationPermissionsDenied.title` | `String` | Title for notification-permissions-denied alert. Defaults to "Notification Permissions Denied". | -| `notificationPermissionsDenied.message` | `String` | Message for notification-permissions-denied alert. | -| `notificationPermissionsDenied.actionButtonTitle` | `String` | Action button title for notification-permissions-denied alert. Defaults to "Open Settings". | -| `notificationPermissionsDenied.closeButtonTitle` | `String` | Close button title for notification-permissions-denied alert. Defaults to "Not now". | -| `shouldShowPurchaseFailureAlert` | `Bool` | Shows an alert after a purchase fails. Set to `false` if you handle failures via a `PurchaseController`. Defaults to `true`. | -| `shouldPreload` | `Bool` | Preloads and caches trigger paywalls and products during SDK initialization. Defaults to `true`. | -| `automaticallyDismiss` | `Bool` | Automatically dismisses the paywall on successful purchase or restore. Defaults to `true`. | -| `overrideProductsByName` | `[String: String]?` | Overrides products on all paywalls using name→identifier mapping (e.g., `"primary"` → `"com.example.premium_monthly"`). | -| `shouldShowWebPurchaseConfirmationAlert` | `Bool` | Shows a localized alert confirming a successful web checkout purchase. Defaults to `true`. | -| `transactionBackgroundView` | `TransactionBackgroundView` | View shown behind the system payment sheet during a transaction. `.spinner` by default; set to `.none` to remove. | - + + ## Usage ```swift diff --git a/content/docs/ios/sdk-reference/PaywallPresentationHandler.mdx b/content/docs/ios/sdk-reference/PaywallPresentationHandler.mdx index c67e595..7a669ea 100644 --- a/content/docs/ios/sdk-reference/PaywallPresentationHandler.mdx +++ b/content/docs/ios/sdk-reference/PaywallPresentationHandler.mdx @@ -27,15 +27,36 @@ public final class PaywallPresentationHandler: NSObject { ``` ## Parameters - -| Method | Parameters | Description | -|--------|------------|-------------| -| `onPresent` | `handler: (PaywallInfo) -> Void` | Sets a handler called when the paywall is presented. | -| `onWillDismiss` | `handler: (PaywallInfo, PaywallResult) -> Void` | Sets a handler called when the paywall will be dismissed. Available in version 4.9.0+. | -| `onDismiss` | `handler: (PaywallInfo, PaywallResult) -> Void` | Sets a handler called when the paywall is dismissed. | -| `onSkip` | `handler: (PaywallSkippedReason) -> Void` | Sets a handler called when paywall presentation is skipped. | -| `onError` | `handler: (Error) -> Void` | Sets a handler called when an error occurs during presentation. | - + Void", + description: "Sets a handler called when the paywall is presented.", + required: true, + }, + onWillDismiss: { + type: "handler: (PaywallInfo, PaywallResult) -> Void", + description: "Sets a handler called when the paywall will be dismissed. Available in version 4.9.0+.", + required: true, + }, + onDismiss: { + type: "handler: (PaywallInfo, PaywallResult) -> Void", + description: "Sets a handler called when the paywall is dismissed.", + required: true, + }, + onSkip: { + type: "handler: (PaywallSkippedReason) -> Void", + description: "Sets a handler called when paywall presentation is skipped.", + required: true, + }, + onError: { + type: "handler: (Error) -> Void", + description: "Sets a handler called when an error occurs during presentation.", + required: true, + }, + }} +/> + ## Returns / State Each method returns `Void` and configures the handler for the specific paywall lifecycle event. diff --git a/content/docs/ios/sdk-reference/PurchaseController.mdx b/content/docs/ios/sdk-reference/PurchaseController.mdx index dfe954c..77f5ad9 100644 --- a/content/docs/ios/sdk-reference/PurchaseController.mdx +++ b/content/docs/ios/sdk-reference/PurchaseController.mdx @@ -26,12 +26,21 @@ public protocol PurchaseController: AnyObject { ``` ## Parameters - -| Method | Parameters | Return Type | Description | -|--------|------------|-------------|-------------| -| `purchase` | `product: StoreProduct` | `PurchaseResult` | Called when user initiates purchasing. Implement your purchase logic here. | -| `restorePurchases` | None | `RestorationResult` | Called when user initiates restore. Implement your restore logic here. | - + + ## Returns / State - `purchase()` returns a `PurchaseResult` (`.purchased`, `.failed(Error)`, or `.cancelled`) @@ -44,4 +53,4 @@ For implementation examples and detailed guidance, see [Using RevenueCat](/ios/g This is commonly used with RevenueCat, StoreKit 2, or other third-party purchase frameworks where you want to maintain your existing purchase logic. - \ No newline at end of file + diff --git a/content/docs/ios/sdk-reference/SubscriptionTransaction.mdx b/content/docs/ios/sdk-reference/SubscriptionTransaction.mdx index d40af36..864619a 100644 --- a/content/docs/ios/sdk-reference/SubscriptionTransaction.mdx +++ b/content/docs/ios/sdk-reference/SubscriptionTransaction.mdx @@ -12,22 +12,68 @@ Provides details about a single subscription transaction returned from [`Custome ## Properties - -| Property | Type | Description | -|----------|------|-------------| -| `transactionId` | `String` | Unique identifier for the transaction. | -| `productId` | `String` | The product identifier for the subscription. | -| `purchaseDate` | `Date` | When the App Store charged the account. | -| `willRenew` | `Bool` | Whether the subscription is set to auto-renew. | -| `isRevoked` | `Bool` | `true` if the transaction has been revoked. | -| `isInGracePeriod` | `Bool` | `true` if the subscription is in grace period. | -| `isInBillingRetryPeriod` | `Bool` | `true` if the subscription is in billing retry. | -| `isActive` | `Bool` | `true` when the subscription is currently active. | -| `expirationDate` | `Date?` | Expiration date, if applicable. | -| `offerType` | `LatestSubscription.OfferType?` | Offer applied to this transaction (4.11.0+). | -| `subscriptionGroupId` | `String?` | Subscription group identifier if available (4.11.0+). | -| `store` | `ProductStore` | Store that fulfilled the purchase (4.11.0+). | - + + ### Offer types (4.11.0+) - `trial` — introductory offer. diff --git a/content/docs/ios/sdk-reference/SuperwallDelegate.mdx b/content/docs/ios/sdk-reference/SuperwallDelegate.mdx index c5d5e8c..b3efcde 100644 --- a/content/docs/ios/sdk-reference/SuperwallDelegate.mdx +++ b/content/docs/ios/sdk-reference/SuperwallDelegate.mdx @@ -70,19 +70,56 @@ public protocol SuperwallDelegate: AnyObject { ## Parameters All methods are optional to implement. Key methods include: - -| Method | Parameters | Description | -|--------|------------|-------------| -| `subscriptionStatusDidChange` | `oldValue`, `newValue` | Called when subscription status changes. | -| `handleSuperwallEvent` | `eventInfo` | Called for all internal analytics events. Use for tracking in your own analytics. | -| `handleCustomPaywallAction` | `name` | Called when user taps elements with `data-pw-custom` tags. | -| `willPresentPaywall` | `paywallInfo` | Called before paywall presentation. | -| `didPresentPaywall` | `paywallInfo` | Called after paywall presentation. | -| `willDismissPaywall` | `paywallInfo` | Called before paywall dismissal. | -| `didDismissPaywall` | `paywallInfo` | Called after paywall dismissal. | -| `customerInfoDidChange` | `oldValue`, `newValue` | Called when customer info changes. Available in version 4.10.0+. | -| `userAttributesDidChange` | `newAttributes` | Called when user attributes change outside your app (for example via the “Set user attributes” paywall action). | - + + ## Returns / State All delegate methods return `Void`. They provide information about Superwall events and state changes. @@ -180,4 +217,4 @@ func userAttributesDidChange(newAttributes: [String: Any]) { // React to server-driven or paywall-triggered updates refreshProfileUI(with: newAttributes) } -``` \ No newline at end of file +``` diff --git a/content/docs/ios/sdk-reference/SuperwallOptions.mdx b/content/docs/ios/sdk-reference/SuperwallOptions.mdx index 4259866..087e858 100644 --- a/content/docs/ios/sdk-reference/SuperwallOptions.mdx +++ b/content/docs/ios/sdk-reference/SuperwallOptions.mdx @@ -32,16 +32,41 @@ public final class SuperwallOptions: NSObject { ``` ## Parameters - -| Property | Type | Description | -|----------|------|-------------| -| `paywalls` | [`PaywallOptions`](/ios/sdk-reference/PaywallOptions) | Configuration for paywall appearance and behavior. | -| `storeKitVersion` | `StoreKitVersion` | Preferred StoreKit version (`.storeKit1` or `.storeKit2`). Defaults to StoreKit 2 on iOS 15+. | -| `networkEnvironment` | `NetworkEnvironment` | Network environment (`.release`, `.releaseCandidate`, `.developer`, `.custom(String)`). **Use only if instructed by Superwall team.** | -| `logging` | `LoggingOptions` | Logging configuration including level and scopes. | -| `localeIdentifier` | `String?` | Override locale for paywall localization (e.g., "en_GB"). | -| `shouldBypassAppTransactionCheck` | `Bool` | Disables the app transaction check on SDK launch. Useful in testing environments to avoid triggering the Apple ID sign-in prompt. Defaults to `false`. Available in version 4.9.0+. | - + + ## Returns / State This is a configuration object used when calling [`configure()`](/ios/sdk-reference/configure). diff --git a/content/docs/ios/sdk-reference/advanced/getPaywall.mdx b/content/docs/ios/sdk-reference/advanced/getPaywall.mdx index 592183b..16f98c7 100644 --- a/content/docs/ios/sdk-reference/advanced/getPaywall.mdx +++ b/content/docs/ios/sdk-reference/advanced/getPaywall.mdx @@ -36,15 +36,36 @@ public func getPaywall( ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `placement` | `String` | The name of the placement as defined on the Superwall dashboard. | -| `params` | `[String: Any]?` | Optional parameters to pass with your placement for audience filters. Keys beginning with `$` are reserved and will be dropped. Defaults to `nil`. | -| `paywallOverrides` | `PaywallOverrides?` | Optional overrides for products and presentation style. Defaults to `nil`. | -| `delegate` | `PaywallViewControllerDelegate` | A delegate to handle user interactions with the retrieved PaywallViewController. | -| `completion` | `@escaping (PaywallViewController?, PaywallSkippedReason?, Error?) -> Void` | Completion block for the callback version. | - + Void", + description: "Completion block for the callback version.", + required: true, + }, + }} +/> + ## Returns / State Returns a `PaywallViewController` that you can present. If presentation should be skipped, throws a `PaywallSkippedReason` error. diff --git a/content/docs/ios/sdk-reference/configure.mdx b/content/docs/ios/sdk-reference/configure.mdx index 5ffaab5..828c718 100644 --- a/content/docs/ios/sdk-reference/configure.mdx +++ b/content/docs/ios/sdk-reference/configure.mdx @@ -21,14 +21,32 @@ public static func configure( ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `apiKey` | `String` | Your Public API Key from the Superwall dashboard settings. | -| `purchaseController` | `PurchaseController?` | Optional object for handling all subscription-related logic yourself. If `nil`, Superwall handles subscription logic. Defaults to `nil`. | -| `options` | `SuperwallOptions?` | Optional configuration object for customizing paywall appearance and behavior. See [`SuperwallOptions`](/ios/sdk-reference/SuperwallOptions) for details. Defaults to `nil`. | -| `completion` | `(() -> Void)?` | Optional completion handler called when Superwall finishes configuring. Defaults to `nil`. | - + Void)?", + description: "Optional completion handler called when Superwall finishes configuring.", + default: "nil", + }, + }} +/> + ## Returns / State Returns the configured `Superwall` instance. The instance is also accessible via [`Superwall.shared`](/ios/sdk-reference/Superwall). @@ -59,4 +77,4 @@ Superwall.configure( apiKey: "pk_your_api_key", purchaseController: MyPurchaseController() ) -``` \ No newline at end of file +``` diff --git a/content/docs/ios/sdk-reference/customerInfo.mdx b/content/docs/ios/sdk-reference/customerInfo.mdx index d65dbde..1bfd1f8 100644 --- a/content/docs/ios/sdk-reference/customerInfo.mdx +++ b/content/docs/ios/sdk-reference/customerInfo.mdx @@ -22,15 +22,36 @@ public var customerInfo: CustomerInfo { get } ## Properties - -| Property | Type | Description | -|----------|------|-------------| -| `subscriptions` | `[SubscriptionTransaction]` | All subscription transactions, ordered by purchase date (ascending). | -| `nonSubscriptions` | `[NonSubscriptionTransaction]` | All non-subscription transactions (consumables and non-consumables), ordered by purchase date (ascending). | -| `entitlements` | `[Entitlement]` | All entitlements available to the user. | -| `activeSubscriptionProductIds` | `Set` | Product identifiers for active subscriptions. | -| `userId` | `String` | The ID of the user. Equivalent to [`Superwall.userId`](/ios/sdk-reference/userId). | - +", + description: "Product identifiers for active subscriptions.", + required: true, + }, + userId: { + type: "String", + description: "The ID of the user. Equivalent to Superwall.userId.", + required: true, + }, + }} +/> + Starting in 4.11.0, transactions include offer metadata (`offerType`), the `subscriptionGroupId`, and the `store` (`ProductStore`) that fulfilled the purchase to help you audit cross-store sales. diff --git a/content/docs/ios/sdk-reference/entitlements.mdx b/content/docs/ios/sdk-reference/entitlements.mdx index ca16414..d740773 100644 --- a/content/docs/ios/sdk-reference/entitlements.mdx +++ b/content/docs/ios/sdk-reference/entitlements.mdx @@ -17,16 +17,41 @@ public var entitlements: EntitlementsInfo { get } ## Properties and Methods - -| Property/Method | Type | Description | -|----------------|------|-------------| -| `active` | `Set` | The active entitlements. | -| `inactive` | `Set` | The inactive entitlements. | -| `all` | `Set` | All entitlements, regardless of whether they're active or not. | -| `web` | `Set` | Active entitlements redeemed via the web. | -| `byProductId(_:)` | `(String) -> Set` | Returns entitlements for a given product ID. | -| `byProductIds(_:)` | `(Set) -> Set` | Returns entitlements for a given set of product IDs. Available in version 4.10.0+. | - +", + description: "The active entitlements.", + required: true, + }, + inactive: { + type: "Set", + description: "The inactive entitlements.", + required: true, + }, + all: { + type: "Set", + description: "All entitlements, regardless of whether they're active or not.", + required: true, + }, + web: { + type: "Set", + description: "Active entitlements redeemed via the web.", + required: true, + }, + "byProductId(_:)": { + type: "(String) -> Set", + description: "Returns entitlements for a given product ID.", + required: true, + }, + "byProductIds(_:)": { + type: "(Set) -> Set", + description: "Returns entitlements for a given set of product IDs. Available in version 4.10.0+.", + required: true, + }, + }} +/> + ## Returns / State Returns an `EntitlementsInfo` object that provides access to entitlements and methods to query them. diff --git a/content/docs/ios/sdk-reference/getPresentationResult.mdx b/content/docs/ios/sdk-reference/getPresentationResult.mdx index 0a58631..2a3c80a 100644 --- a/content/docs/ios/sdk-reference/getPresentationResult.mdx +++ b/content/docs/ios/sdk-reference/getPresentationResult.mdx @@ -23,12 +23,21 @@ public func getPresentationResult( ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `placement` | `String` | The placement you want to evaluate. | -| `params` | `[String: Any]?` | Optional parameters passed to audience filters. Keys starting with `$` are reserved by Superwall and removed. Nested dictionaries and arrays are ignored. Defaults to `nil`. | - + + ## Returns / State Returns a `PresentationResult`, which can be: diff --git a/content/docs/ios/sdk-reference/handleDeepLink.mdx b/content/docs/ios/sdk-reference/handleDeepLink.mdx index a35f5a6..e46ad4e 100644 --- a/content/docs/ios/sdk-reference/handleDeepLink.mdx +++ b/content/docs/ios/sdk-reference/handleDeepLink.mdx @@ -21,11 +21,16 @@ public static func handleDeepLink(_ url: URL) -> Bool ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `url` | `URL` | The deep link URL to process for paywall triggers. | - + + ## Returns / State Returns `true` when Superwall will handle the URL. If called before `Superwall.configure(...)` completes, it only returns `true` for known Superwall URL formats or when cached config contains a `deepLink_open` trigger. Use the return value to continue your own deep-link handling when it is `false`. diff --git a/content/docs/ios/sdk-reference/identify.mdx b/content/docs/ios/sdk-reference/identify.mdx index 02d07b3..6b8a9a9 100644 --- a/content/docs/ios/sdk-reference/identify.mdx +++ b/content/docs/ios/sdk-reference/identify.mdx @@ -19,12 +19,21 @@ public func identify( ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `userId` | `String` | Your user's unique identifier, as defined by your backend system. | -| `options` | `IdentityOptions?` | Optional configuration for identity behavior. Set `restorePaywallAssignments` to `true` to wait for paywall assignments from the server. Use only in advanced cases where users frequently switch accounts. Defaults to `nil`. | - + + `appAccountToken` must be a UUID to be accepted by StoreKit. diff --git a/content/docs/ios/sdk-reference/register.mdx b/content/docs/ios/sdk-reference/register.mdx index fa94289..b788960 100644 --- a/content/docs/ios/sdk-reference/register.mdx +++ b/content/docs/ios/sdk-reference/register.mdx @@ -25,14 +25,30 @@ public func register( ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `placement` | `String` | The name of the placement you wish to register. | -| `params` | `[String: Any]?` | Optional parameters to pass with your placement. These can be referenced within audience filters in your campaign. Keys beginning with `$` are reserved for Superwall and will be dropped. Arrays and dictionaries are currently unsupported and will be ignored. Defaults to `nil`. | -| `handler` | `PaywallPresentationHandler?` | A handler whose functions provide status updates for the paywall lifecycle. Defaults to `nil`. | -| `feature` | `(() -> Void)?` | An optional completion block representing the gated feature. It is executed based on the paywall's gating mode: called immediately for **Non-Gated**, called after the user subscribes or if already subscribed for **Gated**. | - + Void)?", + description: "An optional completion block representing the gated feature. It is executed based on the paywall's gating mode: called immediately for **Non-Gated**, called after the user subscribes or if already subscribed for **Gated**.", + }, + }} +/> + ## Returns / State This function returns `Void`. If you supply a `feature` block, it will be executed according to the paywall's gating configuration, as described above. diff --git a/content/docs/ios/sdk-reference/setIntegrationAttributes.mdx b/content/docs/ios/sdk-reference/setIntegrationAttributes.mdx index bf2ed72..447d1b1 100644 --- a/content/docs/ios/sdk-reference/setIntegrationAttributes.mdx +++ b/content/docs/ios/sdk-reference/setIntegrationAttributes.mdx @@ -17,11 +17,16 @@ public func setIntegrationAttributes(_ props: [IntegrationAttribute: String?]) ## Parameters - -| Parameter | Type | Description | -|-----------|------|-------------| -| `props` | `[IntegrationAttribute: String?]` | A dictionary mapping integration attribute keys to their values. Use `nil` to remove an attribute. | - + + ## Returns / State This method returns `Void`. The attributes are stored and sent to Superwall's servers. diff --git a/content/docs/ios/sdk-reference/setUserAttributes.mdx b/content/docs/ios/sdk-reference/setUserAttributes.mdx index 22e34da..38dc073 100644 --- a/content/docs/ios/sdk-reference/setUserAttributes.mdx +++ b/content/docs/ios/sdk-reference/setUserAttributes.mdx @@ -20,11 +20,16 @@ public func setUserAttributes(_ attributes: [String: Any?]) ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `attributes` | `[String: Any?]` | A dictionary of custom attributes to store for the user. Values can be any JSON encodable value, URLs, or Dates. | - + + ## Returns / State This function returns `Void`. If an attribute already exists, its value will be overwritten while other attributes remain unchanged. diff --git a/content/docs/react-native/sdk-reference/PaywallOptions.mdx b/content/docs/react-native/sdk-reference/PaywallOptions.mdx index 5c160cd..8f225e3 100644 --- a/content/docs/react-native/sdk-reference/PaywallOptions.mdx +++ b/content/docs/react-native/sdk-reference/PaywallOptions.mdx @@ -32,16 +32,41 @@ export class PaywallOptions { ## Properties - -| Property | Type | Description | -|---------|------|-------------| -| `isHapticFeedbackEnabled` | `boolean` | Whether haptic feedback is enabled when interacting with paywalls. Defaults to `true`. | -| `restoreFailed` | `RestoreFailed` | Configuration for the alert shown when restore purchases fails. | -| `shouldShowPurchaseFailureAlert` | `boolean` | Whether to show an alert when a purchase fails. Defaults to `true`. | -| `shouldPreload` | `boolean` | Whether paywalls should be preloaded. If `false`, you can manually preload using `preloadAllPaywalls()` or `preloadPaywalls()`. Defaults to `false`. | -| `automaticallyDismiss` | `boolean` | Whether paywalls should automatically dismiss after a successful purchase. Defaults to `true`. | -| `transactionBackgroundView` | `TransactionBackgroundView` | The view to show behind Apple's payment sheet during a transaction. Options: `spinner`, `none`. Defaults to `spinner`. | - + + ## Usage diff --git a/content/docs/react-native/sdk-reference/PaywallPresentationHandler.mdx b/content/docs/react-native/sdk-reference/PaywallPresentationHandler.mdx index de28c32..afab78c 100644 --- a/content/docs/react-native/sdk-reference/PaywallPresentationHandler.mdx +++ b/content/docs/react-native/sdk-reference/PaywallPresentationHandler.mdx @@ -31,14 +31,31 @@ export class PaywallPresentationHandler { ## Methods - -| Method | Parameters | Description | -|--------|------------|-------------| -| `onPresent` | `handler: (info: PaywallInfo) => void` | Sets a handler that is called when a paywall is presented. | -| `onDismiss` | `handler: (info: PaywallInfo, result: PaywallResult) => void` | Sets a handler that is called when a paywall is dismissed. | -| `onError` | `handler: (error: string) => void` | Sets a handler that is called when an error occurs during paywall presentation. | -| `onSkip` | `handler: (reason: PaywallSkippedReason) => void` | Sets a handler that is called when a paywall is skipped (not shown). | - + void", + description: "Sets a handler that is called when a paywall is presented.", + required: true, + }, + onDismiss: { + type: "handler: (info: PaywallInfo, result: PaywallResult) => void", + description: "Sets a handler that is called when a paywall is dismissed.", + required: true, + }, + onError: { + type: "handler: (error: string) => void", + description: "Sets a handler that is called when an error occurs during paywall presentation.", + required: true, + }, + onSkip: { + type: "handler: (reason: PaywallSkippedReason) => void", + description: "Sets a handler that is called when a paywall is skipped (not shown).", + required: true, + }, + }} +/> + ## Usage diff --git a/content/docs/react-native/sdk-reference/PurchaseController.mdx b/content/docs/react-native/sdk-reference/PurchaseController.mdx index 058a848..5c768f5 100644 --- a/content/docs/react-native/sdk-reference/PurchaseController.mdx +++ b/content/docs/react-native/sdk-reference/PurchaseController.mdx @@ -29,13 +29,26 @@ export abstract class PurchaseController { ## Methods - -| Method | Parameters | Description | -|--------|------------|-------------| -| `purchaseFromAppStore` | `productId` | Purchase a product from the App Store. Returns a Promise that resolves with the result of the purchase logic. | -| `purchaseFromGooglePlay` | `productId`, `basePlanId?`, `offerId?` | Purchase a product from Google Play. Returns a Promise that resolves with the result of the purchase logic. | -| `restorePurchases` | - | Restore purchases. Returns a Promise that resolves with the restoration result. | - + + ## Usage diff --git a/content/docs/react-native/sdk-reference/SuperwallDelegate.mdx b/content/docs/react-native/sdk-reference/SuperwallDelegate.mdx index 5ac66c0..6c2e9a2 100644 --- a/content/docs/react-native/sdk-reference/SuperwallDelegate.mdx +++ b/content/docs/react-native/sdk-reference/SuperwallDelegate.mdx @@ -54,22 +54,71 @@ export abstract class SuperwallDelegate { All methods must be implemented (you can provide empty bodies). Key methods include: - -| Method | Parameters | Description | -|--------|------------|-------------| -| `subscriptionStatusDidChange` | `from`, `to` | Called when subscription status changes. | -| `handleSuperwallEvent` | `eventInfo` | Called for all internal analytics events. Use for tracking in your own analytics. | -| `handleCustomPaywallAction` | `name` | Called when user taps elements with `data-pw-custom` tags. | -| `willPresentPaywall` | `paywallInfo` | Called before paywall presentation. | -| `didPresentPaywall` | `paywallInfo` | Called after paywall presentation. | -| `willDismissPaywall` | `paywallInfo` | Called before paywall dismissal. | -| `didDismissPaywall` | `paywallInfo` | Called after paywall dismissal. | -| `paywallWillOpenURL` | `url` | Called when paywall attempts to open a URL. | -| `paywallWillOpenDeepLink` | `url` | Called when paywall attempts to open a deep link. | -| `handleLog` | `level`, `scope`, `message`, `info`, `error` | Called for logging messages from the SDK. | -| `willRedeemLink` | - | Called before the SDK attempts to redeem a promotional link. | -| `didRedeemLink` | `result` | Called after the SDK has attempted to redeem a promotional link. | - +, error?: string", + description: "Called for logging messages from the SDK.", + required: true, + }, + willRedeemLink: { + type: "None", + description: "Called before the SDK attempts to redeem a promotional link.", + required: true, + }, + didRedeemLink: { + type: "result: RedemptionResult", + description: "Called after the SDK has attempted to redeem a promotional link.", + required: true, + }, + }} +/> + ## Usage @@ -154,4 +203,3 @@ handleSuperwallEvent(eventInfo: SuperwallEventInfo) { } } ``` - diff --git a/content/docs/react-native/sdk-reference/SuperwallOptions.mdx b/content/docs/react-native/sdk-reference/SuperwallOptions.mdx index 6a9ffd5..b2240c4 100644 --- a/content/docs/react-native/sdk-reference/SuperwallOptions.mdx +++ b/content/docs/react-native/sdk-reference/SuperwallOptions.mdx @@ -32,20 +32,61 @@ export class SuperwallOptions { ## Properties - -| Property | Type | Description | -|---------|------|-------------| -| `paywalls` | `PaywallOptions` | Options for configuring paywall appearance and behavior. See [`PaywallOptions`](/react-native/sdk-reference/PaywallOptions) for details. | -| `networkEnvironment` | `NetworkEnvironment` | The network environment to use. Options: `Release`, `ReleaseCandidate`, `Developer`. Defaults to `Release`. | -| `isExternalDataCollectionEnabled` | `boolean` | Whether external data collection is enabled. Defaults to `true`. | -| `localeIdentifier` | `string?` | The locale identifier to use. If not set, the system locale is used. | -| `isGameControllerEnabled` | `boolean` | Whether game controller support is enabled. Defaults to `false`. | -| `logging` | `LoggingOptions` | Options for configuring logging behavior. **Must be an instance of `LoggingOptions` so `toJson()` is available.** | -| `collectAdServicesAttribution` | `boolean` | Whether to collect AdServices attribution data. Defaults to `false`. | -| `passIdentifiersToPlayStore` | `boolean` | Whether to pass identifiers to Play Store. Defaults to `false`. | -| `storeKitVersion` | `"STOREKIT1" \| "STOREKIT2"?` | The StoreKit version to use (iOS only). Defaults to `undefined` (auto-detect). | -| `enableExperimentalDeviceVariables` | `boolean` | Whether to enable experimental device variables. Defaults to `false`. | - + + ## Usage @@ -103,4 +144,3 @@ const prodOptions = new SuperwallOptions({ - [`PaywallOptions`](/react-native/sdk-reference/PaywallOptions) - Paywall-specific options - [`configure()`](/react-native/sdk-reference/configure) - Configure the SDK with options - diff --git a/content/docs/react-native/sdk-reference/configure.mdx b/content/docs/react-native/sdk-reference/configure.mdx index 6e46e67..132faf5 100644 --- a/content/docs/react-native/sdk-reference/configure.mdx +++ b/content/docs/react-native/sdk-reference/configure.mdx @@ -34,14 +34,32 @@ static async configure({ ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `apiKey` | `string` | Your Public API Key from the Superwall dashboard settings. | -| `options` | `SuperwallOptions?` | Optional configuration object for customizing paywall appearance and behavior. See [`SuperwallOptions`](/react-native/sdk-reference/SuperwallOptions) for details. Defaults to `undefined`. | -| `purchaseController` | `PurchaseController?` | Optional object for handling all subscription-related logic yourself. If omitted, Superwall handles subscription logic. Defaults to `undefined`. | -| `completion` | `(() => void)?` | Optional completion handler called when Superwall finishes configuring. Defaults to `undefined`. | - + void)?", + description: "Optional completion handler called when Superwall finishes configuring.", + default: "undefined", + }, + }} +/> + ## Returns / State Returns a Promise that resolves to the configured `Superwall` instance. The instance is also accessible via [`Superwall.shared`](/react-native/sdk-reference/Superwall). @@ -99,5 +117,3 @@ await Superwall.configure({ purchaseController: new MyPurchaseController() }) ``` - - diff --git a/content/docs/react-native/sdk-reference/handleDeepLink.mdx b/content/docs/react-native/sdk-reference/handleDeepLink.mdx index b43ef9d..467f209 100644 --- a/content/docs/react-native/sdk-reference/handleDeepLink.mdx +++ b/content/docs/react-native/sdk-reference/handleDeepLink.mdx @@ -20,11 +20,16 @@ async handleDeepLink(url: string): Promise ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `url` | `string` | The deep link URL to handle. | - + + ## Returns / State Returns a Promise that resolves to a boolean indicating whether the deep link was handled by Superwall. Returns `true` if Superwall handled the link, `false` otherwise. diff --git a/content/docs/react-native/sdk-reference/identify.mdx b/content/docs/react-native/sdk-reference/identify.mdx index 8078e1d..c813894 100644 --- a/content/docs/react-native/sdk-reference/identify.mdx +++ b/content/docs/react-native/sdk-reference/identify.mdx @@ -26,12 +26,21 @@ async identify({ ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `userId` | `string` | Your user's unique identifier as defined by your backend system. | -| `options` | `IdentityOptions?` | An optional `IdentityOptions` object. You can set the `restorePaywallAssignments` property to `true` to instruct the SDK to wait to restore paywall assignments from the server before presenting any paywalls. This option should be used only in advanced cases (e.g., when users frequently switch accounts or reinstall the app). Defaults to `undefined`. | - + + ## Returns / State Returns a Promise that resolves once the identification process is complete. diff --git a/content/docs/react-native/sdk-reference/register.mdx b/content/docs/react-native/sdk-reference/register.mdx index 2ce4816..a50d36b 100644 --- a/content/docs/react-native/sdk-reference/register.mdx +++ b/content/docs/react-native/sdk-reference/register.mdx @@ -25,14 +25,30 @@ async register(params: { ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `placement` | `string` | The name of the placement you wish to register. | -| `params` | `Map \| Record?` | Optional parameters to pass with your placement. These can be referenced within audience filters in your campaign. Keys beginning with `$` are reserved for Superwall and will be dropped. Arrays and dictionaries as values are not supported and will be omitted. Defaults to `undefined`. | -| `handler` | `PaywallPresentationHandler?` | A handler whose functions provide status updates for the paywall lifecycle. Defaults to `undefined`. | -| `feature` | `(() => void)?` | An optional completion callback representing the gated feature. It is executed based on the paywall's gating mode: called immediately for **Non-Gated**, called after the user subscribes or if already subscribed for **Gated**. If not provided, you can chain a `.then()` block to the returned promise. | - + | Record?", + description: "Optional parameters to pass with your placement. These can be referenced within audience filters in your campaign. Keys beginning with `$` are reserved for Superwall and will be dropped. Arrays and dictionaries as values are not supported and will be omitted.", + default: "undefined", + }, + handler: { + type: "PaywallPresentationHandler?", + description: "A handler whose functions provide status updates for the paywall lifecycle.", + default: "undefined", + }, + feature: { + type: "(() => void)?", + description: "An optional completion callback representing the gated feature. It is executed based on the paywall's gating mode: called immediately for **Non-Gated**, called after the user subscribes or if already subscribed for **Gated**. If not provided, you can chain a `.then()` block to the returned promise.", + }, + }} +/> + ## Returns / State Returns a Promise that resolves when registration completes. If you supply a `feature` callback, it will be executed according to the paywall's gating configuration, as described above. diff --git a/content/docs/react-native/sdk-reference/setUserAttributes.mdx b/content/docs/react-native/sdk-reference/setUserAttributes.mdx index 3fc2df9..2f22cfa 100644 --- a/content/docs/react-native/sdk-reference/setUserAttributes.mdx +++ b/content/docs/react-native/sdk-reference/setUserAttributes.mdx @@ -24,11 +24,15 @@ async setUserAttributes(userAttributes: UserAttributes): Promise ``` ## Parameters - -| Name | Type | Description | -|------|------|-------------| -| `userAttributes` | `UserAttributes` | An object containing custom attributes to store for the user. Values can be any JSON-encodable value, URLs, or Dates. Keys beginning with `$` are reserved for Superwall and will be dropped. Arrays and dictionaries as values are not supported and will be omitted. | - + + ## Returns / State Returns a Promise that resolves once the user attributes have been updated. diff --git a/content/docs/react-native/sdk-reference/subscriptionStatus.mdx b/content/docs/react-native/sdk-reference/subscriptionStatus.mdx index 55fe3e5..f6f2eec 100644 --- a/content/docs/react-native/sdk-reference/subscriptionStatus.mdx +++ b/content/docs/react-native/sdk-reference/subscriptionStatus.mdx @@ -43,11 +43,16 @@ async setSubscriptionStatus(status: SubscriptionStatus): Promise ``` **Parameters:** - -| Name | Type | Description | -|------|------|-------------| -| `status` | `SubscriptionStatus` | The new subscription status. | - + + **Returns:** A Promise that resolves once the subscription status has been updated. diff --git a/content/docs/support/troubleshooting/4780985851-troubleshooting-expo-sdk.mdx b/content/docs/support/troubleshooting/4780985851-troubleshooting-expo-sdk.mdx index ed24a2f..3521a9c 100644 --- a/content/docs/support/troubleshooting/4780985851-troubleshooting-expo-sdk.mdx +++ b/content/docs/support/troubleshooting/4780985851-troubleshooting-expo-sdk.mdx @@ -2,24 +2,62 @@ title: "Troubleshooting: Expo SDK" --- - + **Important: Expo SDK 53+ Required** This SDK is exclusively compatible with Expo SDK version 53 and newer. For projects using older Expo versions, please use our [**legacy React Native SDK**](https://github.com/superwall/react-native-superwall). - -Common Issues -============= - -* [Products or Prices Not Loading](/support/troubleshooting/products-not-loading) - -* [Paywall Not Showing](/support/troubleshooting/6381986971-paywall-not-showing) - - -Support -======= + + +## Common Issues +- [Products or Prices Not Loading](/support/troubleshooting/products-not-loading) +- [Paywall Not Showing](/support/troubleshooting/6381986971-paywall-not-showing) +- [Paywall Autorotates on Android](#paywall-autorotates-on-android) + +## Paywall Autorotates on Android + +If your app is locked to portrait in `app.json` but the Superwall paywall still rotates on Android, Expo may not be writing the orientation to the `SuperwallPaywallActivity` in `AndroidManifest.xml`. Add a config plugin that sets `android:screenOrientation` for that activity, then run `prebuild`/EAS build so the manifest is regenerated. + +```js +const { withAndroidManifest } = require("@expo/config-plugins"); + +module.exports = function withSuperwallLockedOrientation(config) { + return withAndroidManifest(config, (config) => { + const app = config.modResults.manifest.application[0]; + + if (!app.activity) { + app.activity = []; + } + + // Look for SuperwallPaywallActivity + const paywallActivity = app.activity.find( + (activity) => + activity.$["android:name"] === + "com.superwall.sdk.paywall.view.SuperwallPaywallActivity", + ); + + if (paywallActivity) { + // Override orientation if activity already exists + paywallActivity.$["android:screenOrientation"] = "portrait"; + } else { + // Inject if missing + app.activity.push({ + $: { + "android:name": + "com.superwall.sdk.paywall.view.SuperwallPaywallActivity", + "android:theme": "@style/Theme.MaterialComponents.DayNight.NoActionBar", + "android:configChanges": "orientation|screenSize|keyboardHidden", + "android:screenOrientation": "portrait", + }, + }); + } + + return config; + }); +}; +``` + +## Support We are always improving our SDKs and documentation! The Expo SDK is being actively developed, and we're committed to making it the best way to integrate paywalls into your Expo projects! -If you have any thoughts on how to improve or know of any other causes, please leave feedback below! - -If you have any issues **not** covered here please [contact Support](https://superwall.com/docs/support) or [open an issue on GitHub](https://github.com/superwall/expo-superwall/issues)**.** +If you have any thoughts on how to improve or know of any other causes, please leave feedback below! If you have any issues **not** covered here please [contact Support](https://superwall.com/docs/support) or [open an issue on GitHub](https://github.com/superwall/expo-superwall/issues)**.** diff --git a/content/sdk-reference.mdx.example b/content/sdk-reference.mdx.example index ac9782b..50b8e53 100644 --- a/content/sdk-reference.mdx.example +++ b/content/sdk-reference.mdx.example @@ -37,12 +37,21 @@ Add any additional information here. ``` ## Parameters - - -| Name | Type | Description | -|------|------|-------------| -| `param` | `type` | What the parameter does. | - + + @@ -54,4 +63,4 @@ Describe what the function returns **or** outline the state union if this is a h ```ts // minimal example of how to call or use the API -``` \ No newline at end of file +``` diff --git a/content/shared/testing-purchases.mdx b/content/shared/testing-purchases.mdx index fc2637a..141da4f 100644 --- a/content/shared/testing-purchases.mdx +++ b/content/shared/testing-purchases.mdx @@ -2,8 +2,6 @@ title: "Setting up StoreKit testing" --- -StoreKit testing in Xcode is a local test environment for testing in-app purchases without requiring a connection to App Store servers. Set up in-app purchases in a local StoreKit configuration file in your Xcode project, or create a synced StoreKit configuration file in Xcode from your in-app purchase settings in App Store Connect. After you enable the configuration file, the test environment uses this local data on your paywalls when your app calls StoreKit APIs. - ### Add a StoreKit Configuration File Go to **File ▸ New ▸ File...** in the menu bar , select **StoreKit Configuration File** and hit **Next**: diff --git a/next-env.d.ts b/next-env.d.ts index c4b7818..9edff1c 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/package.json b/package.json index 1276ec3..fd78e06 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,10 @@ "type": "module", "scripts": { "copy:docs-images": "bun run scripts/copy-docs-images.cjs", - "watch:images": "tsx scripts/watch-docs-images.ts", - "generate:md": "tsx scripts/generate-md-files.ts", - "generate:llm": "tsx scripts/generate-llm-files.ts", - "generate:title-map": "tsx scripts/generate-title-map.ts", + "watch:images": "bun scripts/watch-docs-images.ts", + "generate:md": "bun scripts/generate-md-files.ts", + "generate:llm": "bun scripts/generate-llm-files.ts", + "generate:title-map": "bun scripts/generate-title-map.ts", "build:prep": "bun run generate:title-map && bun run generate:llm && bun run generate:md && bun run copy:docs-images", "build": "bun run build:prep && fumadocs-mdx && bun run build:next", "build:next": "NEXT_PRIVATE_STANDALONE=true NEXT_PRIVATE_OUTPUT_TRACE_ROOT=$PWD next build --webpack", @@ -20,7 +20,7 @@ "cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts", "dev": "bun run build:prep && run-p watch:images \"dev:next\"", "dev:next": "next dev --turbo -p 8293", - "clear:cache": "tsx scripts/clear-cache.ts", + "clear:cache": "bun scripts/clear-cache.ts", "start": "next start", "download:references": "bun scripts/download-references.ts", "generate:test-wrangler": "bun scripts/generate-test-wrangler.ts", @@ -29,6 +29,7 @@ "dependencies": { "@ai-sdk/react": "^2.0.109", "@aws-sdk/client-s3": "^3.948.0", + "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.11", "ai": "^5.0.108", "class-variance-authority": "^0.7.1", diff --git a/scripts/generate-llm-files.ts b/scripts/generate-llm-files.ts index be88d78..3381848 100644 --- a/scripts/generate-llm-files.ts +++ b/scripts/generate-llm-files.ts @@ -14,6 +14,7 @@ import remarkDirective from "remark-directive" import { remarkInclude } from 'fumadocs-mdx/config'; import remarkSdkFilter from "../plugins/remark-sdk-filter" import { createProgressBar } from './utils/progress' +import { remarkTypeTableToMarkdown } from './utils/remark-type-table-to-markdown' // 1) Configure your plugins once const processor = remark() @@ -27,6 +28,7 @@ const processor = remark() .use(remarkCodeLanguage as any) .use(remarkCodeGroupToTabs as any) .use(remarkSdkFilter as any) + .use(remarkTypeTableToMarkdown as any) const CONTENT = path.resolve(process.cwd(), 'content/docs') const OUT = path.resolve(process.cwd(), 'public') diff --git a/scripts/generate-md-files.ts b/scripts/generate-md-files.ts index ba6128c..358f0bd 100644 --- a/scripts/generate-md-files.ts +++ b/scripts/generate-md-files.ts @@ -14,6 +14,7 @@ import remarkDirective from "remark-directive" import { remarkInclude } from 'fumadocs-mdx/config'; import remarkSdkFilter from "../plugins/remark-sdk-filter" import { createProgressBar } from './utils/progress' +import { remarkTypeTableToMarkdown } from './utils/remark-type-table-to-markdown' // Configure the processor with all plugins const processor = remark() @@ -27,6 +28,7 @@ const processor = remark() .use(remarkCodeLanguage as any) .use(remarkCodeGroupToTabs as any) .use(remarkSdkFilter as any) + .use(remarkTypeTableToMarkdown as any) const CONTENT = path.resolve(process.cwd(), 'content/docs') const OUT = path.resolve(process.cwd(), 'public') diff --git a/scripts/utils/remark-type-table-to-markdown.ts b/scripts/utils/remark-type-table-to-markdown.ts new file mode 100644 index 0000000..d46d94f --- /dev/null +++ b/scripts/utils/remark-type-table-to-markdown.ts @@ -0,0 +1,187 @@ +import type { Parent } from 'unist' +import { visit } from 'unist-util-visit' + +type MdxAttributeValue = + | { + type: 'mdxJsxAttributeValueExpression' + value: string + data?: { + estree?: { + body?: Array<{ expression?: any }> + } + } + } + | string + | null + +type MdxAttribute = { + type: 'mdxJsxAttribute' + name?: string + value?: MdxAttributeValue +} + +type MdxJsxFlowElement = { + type: 'mdxJsxFlowElement' + name?: string + attributes?: MdxAttribute[] +} + +type TypeTableEntry = Record + +function extractObjectExpression( + node: any, + allowPrimitiveValues = false +): Record | null { + if (!node || node.type !== 'ObjectExpression') return null + + const result: Record = {} + for (const property of node.properties || []) { + if (property.type !== 'Property') continue + if (property.computed) continue + + let key: string | null = null + if (property.key?.type === 'Identifier') { + key = property.key.name + } else if (property.key?.type === 'Literal') { + key = String(property.key.value) + } + if (!key) continue + + const value = extractValue(property.value) + if (value === null || value === undefined) continue + + if (!allowPrimitiveValues && (typeof value !== 'object' || Array.isArray(value))) { + continue + } + + result[key] = value + } + + return result +} + +function extractValue(node: any): unknown { + if (!node) return null + switch (node.type) { + case 'Literal': + return node.value + case 'Identifier': + return node.name + case 'ObjectExpression': + return extractObjectExpression(node, true) + case 'ArrayExpression': + return (node.elements || []).map(extractValue) + default: + return null + } +} + +function formatValue(value: unknown): string { + if (value === null || value === undefined) return '' + if (typeof value === 'string') return value + if (typeof value === 'number' || typeof value === 'boolean') return String(value) + if (Array.isArray(value)) return value.map(formatValue).filter(Boolean).join(', ') + if (typeof value === 'object') return JSON.stringify(value) + return String(value) +} + +function buildTable(entries: Record) { + const values = Object.values(entries) + const hasDefault = values.some((entry) => entry.default !== undefined) + const hasRequired = values.some((entry) => entry.required !== undefined) + + const headers = ['Name', 'Type', 'Description'] + if (hasDefault) headers.push('Default') + if (hasRequired) headers.push('Required') + + const rows = Object.entries(entries).map(([name, entry]) => { + const typeValue = formatValue(entry.type) + const typeLink = formatValue(entry.typeDescriptionLink) + const type = typeLink ? `${typeValue} (see ${typeLink})` : typeValue + + let description = formatValue(entry.description) + const typeDescription = formatValue(entry.typeDescription) + if (typeDescription) { + description = description + ? `${description} Type: ${typeDescription}` + : `Type: ${typeDescription}` + } + + const parameters = Array.isArray(entry.parameters) + ? entry.parameters + .map((param: any) => { + const paramName = formatValue(param?.name) + const paramDesc = formatValue(param?.description) + if (!paramName && !paramDesc) return '' + return `${paramName}${paramDesc ? ` - ${paramDesc}` : ''}` + }) + .filter(Boolean) + : [] + if (parameters.length > 0) { + description = description + ? `${description} Parameters: ${parameters.join('; ')}.` + : `Parameters: ${parameters.join('; ')}.` + } + + const returns = formatValue(entry.returns) + if (returns) { + description = description ? `${description} Returns: ${returns}.` : `Returns: ${returns}.` + } + + if (entry.deprecated === true) { + description = description ? `${description} Deprecated.` : 'Deprecated.' + } + + const row = [name, type, description] + if (hasDefault) row.push(formatValue(entry.default)) + if (hasRequired) row.push(entry.required === true ? 'yes' : 'no') + return row + }) + + return { + type: 'table', + align: [], + children: [ + { + type: 'tableRow', + children: headers.map((header) => ({ + type: 'tableCell', + children: [{ type: 'text', value: header }], + })), + }, + ...rows.map((row) => ({ + type: 'tableRow', + children: row.map((cell) => ({ + type: 'tableCell', + children: [{ type: 'text', value: cell }], + })), + })), + ], + } +} + +export function remarkTypeTableToMarkdown() { + return (tree: Parent) => { + visit(tree, 'mdxJsxFlowElement', (node: MdxJsxFlowElement, index, parent) => { + if (!parent || index === null || node.name !== 'TypeTable') return + + const typeAttribute = node.attributes?.find((attr) => attr.name === 'type') + if (!typeAttribute || !typeAttribute.value) return + + if ( + typeof typeAttribute.value !== 'object' || + typeAttribute.value.type !== 'mdxJsxAttributeValueExpression' + ) { + return + } + + const estree = typeAttribute.value.data?.estree + const expression = estree?.body?.[0]?.expression + const entries = extractObjectExpression(expression) as Record | null + if (!entries) return + + const parentNode = parent as Parent + parentNode.children.splice(index, 1, buildTable(entries)) + }) + } +} diff --git a/src/app/(docs)/[[...slug]]/page.tsx b/src/app/(docs)/[[...slug]]/page.tsx index 116cc0d..99da9ce 100644 --- a/src/app/(docs)/[[...slug]]/page.tsx +++ b/src/app/(docs)/[[...slug]]/page.tsx @@ -1,4 +1,4 @@ -import { source } from "@/lib/source" +import { getPageImage, source } from "@/lib/source" import { DocsPage, DocsBody, DocsDescription, DocsTitle } from "fumadocs-ui/page" import { notFound } from "next/navigation" // import { createRelativeLink } from "fumadocs-ui/mdx" @@ -6,6 +6,8 @@ import { getMDXComponents } from "@/mdx-components" import { createElement } from "react" import { Rate } from "@/components/rate" import { CopyPageButton } from "@/components/CopyPageButton" +import type { Metadata } from "next" +import { DEFAULT_DESCRIPTION, SITE_NAME, TWITTER_HANDLE, toMetadataPath } from "@/lib/metadata" export default async function Page(props: { params: Promise<{ slug?: string[] }> }) { const params = await props.params @@ -54,13 +56,45 @@ export async function generateStaticParams() { return source.generateParams() } -export async function generateMetadata(props: { params: Promise<{ slug?: string[] }> }) { +export async function generateMetadata( + props: { params: Promise<{ slug?: string[] }> } +): Promise { const params = await props.params const page = source.getPage(params.slug) if (!page) notFound() + const title = page.data.title || SITE_NAME + const description = page.data.description || DEFAULT_DESCRIPTION + const canonicalPath = toMetadataPath(page.url || "/") || "." + const ogImage = getPageImage(page).url + const ogTitle = `${title} - ${SITE_NAME}` + return { - title: page.data.title, - description: page.data.description, + title, + description, + alternates: { + canonical: canonicalPath, + }, + openGraph: { + title: ogTitle, + description, + siteName: SITE_NAME, + images: [ + { + url: ogImage, + width: 1200, + height: 630, + alt: title, + }, + ], + }, + twitter: { + card: "summary_large_image", + title: ogTitle, + description, + site: TWITTER_HANDLE, + creator: TWITTER_HANDLE, + images: [ogImage], + }, } } diff --git a/src/app/(docs)/sdk/[...slug]/page.tsx b/src/app/(docs)/sdk/[...slug]/page.tsx index 431e112..f211ada 100644 --- a/src/app/(docs)/sdk/[...slug]/page.tsx +++ b/src/app/(docs)/sdk/[...slug]/page.tsx @@ -2,6 +2,12 @@ import Link from "next/link"; import { DocsPage, DocsBody, DocsDescription, DocsTitle } from "fumadocs-ui/page"; import { Card } from "fumadocs-ui/components/card"; import { AppleIcon, AndroidIcon, FlutterIcon, ExpoIcon } from '@/lib/source'; +import type { Metadata } from "next"; +import { SITE_NAME, TWITTER_HANDLE } from "@/lib/metadata"; + +const title = "Choose Your SDK"; +const ogTitle = `${title} - ${SITE_NAME}`; +const description = "Select your SDK to view the specific documentation for this topic."; const PLATFORMS = [ { @@ -37,10 +43,8 @@ export default async function SdkChooser(props: { params: Promise<{ slug: string return ( - Choose Your SDK - - Select your SDK to view the specific documentation for this topic. - + {title} + {description}
{PLATFORMS.map(platform => ( @@ -66,4 +70,40 @@ export default async function SdkChooser(props: { params: Promise<{ slug: string ); -} \ No newline at end of file +} + +export async function generateMetadata( + props: { params: Promise<{ slug: string[] }> } +): Promise { + const params = await props.params; + const slugPath = params.slug?.length ? `sdk/${params.slug.join("/")}` : "sdk"; + + return { + title, + description, + alternates: { + canonical: slugPath, + }, + openGraph: { + title: ogTitle, + description, + siteName: SITE_NAME, + images: [ + { + url: "og/sdk", + width: 1200, + height: 630, + alt: title, + }, + ], + }, + twitter: { + card: "summary_large_image", + title: ogTitle, + description, + site: TWITTER_HANDLE, + creator: TWITTER_HANDLE, + images: ["og/sdk"], + }, + }; +} diff --git a/src/app/ai/page.tsx b/src/app/ai/page.tsx index 1ebcc7f..2fef92f 100644 --- a/src/app/ai/page.tsx +++ b/src/app/ai/page.tsx @@ -1,9 +1,39 @@ import { Suspense } from "react"; import AIPageContent from "./AIPageContent"; +import type { Metadata } from "next"; +import { SITE_NAME, TWITTER_HANDLE } from "@/lib/metadata"; -export const metadata = { - title: "Ask AI", - description: "Get instant answers to your questions about Superwall.", +const title = "Ask AI"; +const ogTitle = `${title} - ${SITE_NAME}`; +const description = "Get instant answers to your questions about Superwall."; + +export const metadata: Metadata = { + title, + description, + alternates: { + canonical: "ai", + }, + openGraph: { + title: ogTitle, + description, + siteName: SITE_NAME, + images: [ + { + url: "og/ai", + width: 1200, + height: 630, + alt: title, + }, + ], + }, + twitter: { + card: "summary_large_image", + title: ogTitle, + description, + site: TWITTER_HANDLE, + creator: TWITTER_HANDLE, + images: ["og/ai"], + }, }; export default function AIPage() { @@ -14,4 +44,4 @@ export default function AIPage() { ); -} \ No newline at end of file +} diff --git a/src/app/global.css b/src/app/global.css index f567daa..8553193 100644 --- a/src/app/global.css +++ b/src/app/global.css @@ -55,23 +55,8 @@ --tw-prose-counters: var(--color-fd-muted-foreground); } -/* colored pill on first column for */ -.fd-param tbody td:first-child code { - background: #74F8F020; - color: #74F8F0; - border-color: #74F8F030; -} - -/* Remove the default vertical spacing that Fumadocs adds around tables - when they’re wrapped in our . */ -.fd-param > .relative { - margin-top: 0 !important; - margin-bottom: 0 !important; -} - - /* kill clicks on the nav-chevron */ [data-role="expand"], svg[data-icon="true"] { /* ← arrow in current Fumadocs build */ pointer-events: none; -} \ No newline at end of file +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index ceb5831..9c667fb 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,14 +1,36 @@ import './global.css'; import { RootProvider } from 'fumadocs-ui/provider/next'; import { Inter } from 'next/font/google'; +import type { Metadata } from 'next'; import type { ReactNode } from 'react'; import { SearchDialogWrapper as SearchDialog } from '../components/SearchDialog'; import { GlobalScripts } from '../components/GlobalScripts'; +import { DEFAULT_DESCRIPTION, SITE_NAME, SITE_URL, TWITTER_HANDLE } from '@/lib/metadata'; const inter = Inter({ subsets: ['latin'], }); +export const metadata: Metadata = { + metadataBase: new URL(SITE_URL), + title: { + default: SITE_NAME, + template: `%s | ${SITE_NAME}`, + }, + description: DEFAULT_DESCRIPTION, + openGraph: { + siteName: SITE_NAME, + type: 'website', + title: SITE_NAME, + description: DEFAULT_DESCRIPTION, + }, + twitter: { + card: 'summary_large_image', + site: TWITTER_HANDLE, + creator: TWITTER_HANDLE, + }, +}; + export default function Layout({ children }: { children: ReactNode }) { return ( diff --git a/src/app/og/ai/route.tsx b/src/app/og/ai/route.tsx new file mode 100644 index 0000000..f468189 --- /dev/null +++ b/src/app/og/ai/route.tsx @@ -0,0 +1,11 @@ +import { renderOgImage } from '@/lib/og'; +import { SITE_NAME } from '@/lib/metadata'; + +const title = 'Ask AI'; +const description = 'Get instant answers to your questions about Superwall.'; + +export const revalidate = false; + +export async function GET() { + return renderOgImage({ title, description, site: SITE_NAME }); +} diff --git a/src/app/og/docs/[...slug]/route.tsx b/src/app/og/docs/[...slug]/route.tsx new file mode 100644 index 0000000..3c970a9 --- /dev/null +++ b/src/app/og/docs/[...slug]/route.tsx @@ -0,0 +1,22 @@ +import { renderOgImage } from '@/lib/og'; +import { source } from '@/lib/source'; +import { notFound } from 'next/navigation'; +import { DEFAULT_DESCRIPTION, SITE_NAME } from '@/lib/metadata'; + +export const revalidate = false; + +export async function GET( + _req: Request, + context: { params: Promise<{ slug: string[] }> }, +) { + const params = await context.params; + const slug = params.slug || []; + const pageSlug = slug.length > 1 ? slug.slice(0, -1) : undefined; + const page = source.getPage(pageSlug); + if (!page) notFound(); + + const title = page.data.title || SITE_NAME; + const description = page.data.description || DEFAULT_DESCRIPTION; + + return renderOgImage({ title, description, site: SITE_NAME }); +} diff --git a/src/app/og/sdk/route.tsx b/src/app/og/sdk/route.tsx new file mode 100644 index 0000000..0f99d14 --- /dev/null +++ b/src/app/og/sdk/route.tsx @@ -0,0 +1,11 @@ +import { renderOgImage } from '@/lib/og'; +import { SITE_NAME } from '@/lib/metadata'; + +const title = 'Choose Your SDK'; +const description = 'Select your SDK to view the specific documentation for this topic.'; + +export const revalidate = false; + +export async function GET() { + return renderOgImage({ title, description, site: SITE_NAME }); +} diff --git a/src/assets/fonts/Inter-Bold.ttf b/src/assets/fonts/Inter-Bold.ttf new file mode 100644 index 0000000..9fb9b75 Binary files /dev/null and b/src/assets/fonts/Inter-Bold.ttf differ diff --git a/src/assets/fonts/Inter-Regular.ttf b/src/assets/fonts/Inter-Regular.ttf new file mode 100644 index 0000000..b7aaca8 Binary files /dev/null and b/src/assets/fonts/Inter-Regular.ttf differ diff --git a/src/assets/fonts/Inter-SemiBold.ttf b/src/assets/fonts/Inter-SemiBold.ttf new file mode 100644 index 0000000..47f8ab1 Binary files /dev/null and b/src/assets/fonts/Inter-SemiBold.ttf differ diff --git a/src/assets/logo.svg b/src/assets/logo.svg new file mode 100644 index 0000000..0e95755 --- /dev/null +++ b/src/assets/logo.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/components/type-table.tsx b/src/components/type-table.tsx new file mode 100644 index 0000000..bc6f674 --- /dev/null +++ b/src/components/type-table.tsx @@ -0,0 +1,188 @@ +'use client'; + +import { ChevronDown } from 'lucide-react'; +import Link from 'fumadocs-core/link'; +import { cva } from 'class-variance-authority'; +import { cn } from '@/lib/utils'; +import { type ReactNode, useState } from 'react'; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from './ui/collapsible'; + +export interface ParameterNode { + name: string; + description: ReactNode; +} + +export interface TypeNode { + /** + * Additional description of the field + */ + description?: ReactNode; + + /** + * type signature (short) + */ + type: ReactNode; + + /** + * type signature (full) + */ + typeDescription?: ReactNode; + + /** + * Optional `href` for the type + */ + typeDescriptionLink?: string; + + default?: ReactNode; + + required?: boolean; + deprecated?: boolean; + + parameters?: ParameterNode[]; + + returns?: ReactNode; +} + +const keyVariants = cva('text-fd-primary', { + variants: { + deprecated: { + true: 'line-through text-fd-primary/50', + }, + }, +}); + +const fieldVariants = cva('text-fd-muted-foreground not-prose pe-2'); + +export function TypeTable({ + type, + propColumnWidth = '25%', +}: { + type: Record; + propColumnWidth?: string; +}) { + return ( +
+
+

Prop

+

Type

+
+ {Object.entries(type).map(([key, value]) => ( + + ))} +
+ ); +} + +function Item({ + name, + item: { + parameters = [], + description, + required = false, + deprecated, + typeDescription, + default: defaultValue, + type, + typeDescriptionLink, + returns, + }, + propColumnWidth, +}: { + name: string; + item: TypeNode; + propColumnWidth: string; +}) { + const [open, setOpen] = useState(false); + + return ( + + + + {name} + {!required && '?'} + + {typeDescriptionLink ? ( + + {type} + + ) : ( + {type} + )} + + + +
+
+ {description} +
+ {typeDescription && ( + <> +

Type

+

{typeDescription}

+ + )} + {defaultValue && ( + <> +

Default

+

{defaultValue}

+ + )} + {parameters.length > 0 && ( + <> +

Parameters

+
+ {parameters.map((param) => ( +
+

+ {param.name} - +

+
+ {param.description} +
+
+ ))} +
+ + )} + {returns && ( + <> +

Returns

+
+ {returns} +
+ + )} +
+
+
+ ); +} diff --git a/src/components/ui/collapsible.tsx b/src/components/ui/collapsible.tsx new file mode 100644 index 0000000..b972be9 --- /dev/null +++ b/src/components/ui/collapsible.tsx @@ -0,0 +1,42 @@ +'use client'; +import * as Primitive from '@radix-ui/react-collapsible'; +import { forwardRef, useEffect, useState } from 'react'; +import { cn } from '@/lib/utils'; + +const Collapsible = Primitive.Root; + +const CollapsibleTrigger = Primitive.CollapsibleTrigger; + +const CollapsibleContent = forwardRef< + HTMLDivElement, + React.ComponentPropsWithoutRef +>(({ children, ...props }, ref) => { + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + return ( + + {children} + + ); +}); + +CollapsibleContent.displayName = Primitive.CollapsibleContent.displayName; + +export { Collapsible, CollapsibleTrigger, CollapsibleContent }; + +export type CollapsibleProps = Primitive.CollapsibleProps; +export type CollapsibleContentProps = Primitive.CollapsibleContentProps; +export type CollapsibleTriggerProps = Primitive.CollapsibleTriggerProps; diff --git a/src/lib/metadata.ts b/src/lib/metadata.ts new file mode 100644 index 0000000..396da8c --- /dev/null +++ b/src/lib/metadata.ts @@ -0,0 +1,10 @@ +export const SITE_URL = 'https://superwall.com/docs/'; +export const SITE_NAME = 'Superwall Docs'; +export const DEFAULT_DESCRIPTION = + 'Guides and references for Superwall SDKs, the dashboard, and integrations.'; +export const TWITTER_HANDLE = '@superwall'; + +export function toMetadataPath(path?: string | null) { + if (!path) return ''; + return path.replace(/^\/+/, ''); +} diff --git a/src/lib/og.tsx b/src/lib/og.tsx new file mode 100644 index 0000000..b84cfbd --- /dev/null +++ b/src/lib/og.tsx @@ -0,0 +1,135 @@ +import { readFile } from 'fs/promises'; +import { ImageResponse } from 'next/og'; +import { Buffer } from 'buffer'; +import { fileURLToPath } from 'url'; +import { DEFAULT_DESCRIPTION, SITE_NAME } from '@/lib/metadata'; + +const assetPath = (path: string) => fileURLToPath(new URL(path, import.meta.url)); + +const toArrayBuffer = (buffer: Buffer) => { + const arrayBuffer = new ArrayBuffer(buffer.byteLength); + new Uint8Array(arrayBuffer).set(buffer); + return arrayBuffer; +}; + +const interRegular = readFile( + assetPath('../assets/fonts/Inter-Regular.ttf'), +).then((buffer) => toArrayBuffer(buffer)); +const interSemiBold = readFile( + assetPath('../assets/fonts/Inter-SemiBold.ttf'), +).then((buffer) => toArrayBuffer(buffer)); +const interBold = readFile( + assetPath('../assets/fonts/Inter-Bold.ttf'), +).then((buffer) => toArrayBuffer(buffer)); +const logoSvg = readFile( + assetPath('../assets/logo.svg'), + 'utf-8', +).then((svg) => `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`); + +function truncateText(value: string, maxLength: number) { + if (value.length <= maxLength) return value; + const ellipsis = '...'; + if (maxLength <= ellipsis.length) return value.slice(0, maxLength); + return `${value.slice(0, maxLength - ellipsis.length).trimEnd()}${ellipsis}`; +} + +type OgImageOptions = { + title: string; + description?: string; + site?: string; +}; + +export async function renderOgImage({ + title, + description, + site = SITE_NAME, +}: OgImageOptions) { + const [regular, semiBold, bold, logo] = await Promise.all([ + interRegular, + interSemiBold, + interBold, + logoSvg, + ]); + + const displayDescription = truncateText( + description?.trim() || DEFAULT_DESCRIPTION, + 180, + ); + const displayTitle = truncateText(title.trim(), 110); + const titleSize = displayTitle.length > 70 ? 52 : 64; + return new ImageResponse( + ( +
+
+ {site} +
+ DOCS +
+
+ +
+
+ {displayTitle} +
+
+ {displayDescription} +
+
+ +
+
+ ), + { + width: 1200, + height: 630, + fonts: [ + { name: 'Inter', data: regular, weight: 400, style: 'normal' }, + { name: 'Inter', data: semiBold, weight: 600, style: 'normal' }, + { name: 'Inter', data: bold, weight: 700, style: 'normal' }, + ], + }, + ); +} diff --git a/src/lib/source.ts b/src/lib/source.ts index 808f4cc..57daa6b 100644 --- a/src/lib/source.ts +++ b/src/lib/source.ts @@ -1,5 +1,5 @@ import { docs } from '@/.source'; -import { loader } from 'fumadocs-core/source'; +import { loader, type InferPageType } from 'fumadocs-core/source'; import { createElement, SVGProps } from 'react'; import { icons, AlertTriangle } from 'lucide-react' @@ -155,3 +155,12 @@ export const source = loader({ return createElement('img', { src: `/resources/${name}`, className: mergedClassName, ...props }); }, }); + +export function getPageImage(page: InferPageType) { + const segments = [...(page.slugs ?? []), 'image.png']; + + return { + segments, + url: `og/docs/${segments.join('/')}`, + }; +} diff --git a/src/mdx-components.tsx b/src/mdx-components.tsx index 36f4484..1bfdb35 100644 --- a/src/mdx-components.tsx +++ b/src/mdx-components.tsx @@ -12,6 +12,7 @@ import { SDKContent } from './components/SDKContent' import { SdkLatestVersion } from './components/SdkLatestVersion' import { GithubInfo as GithubInfoComponent } from 'fumadocs-ui/components/github-info'; import { WhenLoggedIn, WhenLoggedOut, LoginStatusProvider, BasedOnAuth, LoggedIn, LoggedOut } from './components/LoginStatusContext'; +import { TypeTable } from './components/type-table'; // We'll add custom components here @@ -253,10 +254,6 @@ const GithubInfo = ({ owner, repo }: { owner: string, repo: string }) => { return } -const ParamTable = (props: React.HTMLAttributes) => ( -
-); - // use this function to get MDX components, you will need it for rendering MDX export function getMDXComponents(components?: MDXComponents): MDXComponents { return { @@ -280,7 +277,7 @@ export function getMDXComponents(components?: MDXComponents): MDXComponents { SDKContent, SdkLatestVersion, GithubInfo, - ParamTable, + TypeTable, WhenLoggedIn, WhenLoggedOut, LoginStatusProvider, diff --git a/test/remark-type-table-to-markdown.test.ts b/test/remark-type-table-to-markdown.test.ts new file mode 100644 index 0000000..bf7bdd2 --- /dev/null +++ b/test/remark-type-table-to-markdown.test.ts @@ -0,0 +1,51 @@ +import { test, expect } from "bun:test"; +import { remark } from "remark"; +import remarkGfm from "remark-gfm"; +import remarkMdx from "remark-mdx"; +import { remarkTypeTableToMarkdown } from "../scripts/utils/remark-type-table-to-markdown"; + +test("TypeTable renders a markdown table with type metadata", async () => { + const input = ` Void)?", + description: "Optional completion handler called when Superwall finishes configuring.", + default: "nil", + }, + }} +/> +`; + + const output = String( + await remark() + .use(remarkMdx as any) + .use(remarkGfm as any) + .use(remarkTypeTableToMarkdown as any) + .process(input) + ); + + expect(output).toMatch(/\|\s*Name\s*\|\s*Type\s*\|\s*Description\s*\|\s*Default\s*\|\s*Required\s*\|/); + expect(output).toMatch( + /\|\s*apiKey\s*\|\s*String\s*\|\s*Your Public API Key from the Superwall dashboard settings\./ + ); + expect(output).toMatch(/SuperwallOptions\? \(see \/ios\/sdk-reference\/SuperwallOptions\)/); + expect(output).toMatch(/Optional completion handler called when Superwall finishes configuring\./); + expect(output).toMatch(/\|\s*purchaseController\s*\|\s*PurchaseController\?/); + expect(output).toMatch(/\|\s*nil\s*\|\s*no\s*\|/); +});