Skip to content

Commit

Permalink
Add migration docs (#10)
Browse files Browse the repository at this point in the history
* migration docs

* add back VOICE_STATE_UPDATE event

* Updated migration docs

* Update doc path

* Update docs/embedded-app-sdk-v1-migration.md
  • Loading branch information
matthova authored Mar 6, 2024
1 parent 378df59 commit bbae67b
Show file tree
Hide file tree
Showing 2 changed files with 256 additions and 0 deletions.
98 changes: 98 additions & 0 deletions docs/activity-iframe-sdk-v2-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# V2 Migration

Version 2.0.0 represents a breaking change to the shape of the SDK. This document is aimed to help developers migrate their activity code from a v1 release to v2.0.0 and beyond.

If you are migrating from `@discord/activity-iframe-sdk` v2 to `@discord/embedded-app-sdk` v1 please see [this document](/docs/embedded-app-sdk-v1-migration.md) instead.

# Command Migration

The primary change to the SDK in 2.0.0 is a standardization of the command return shape. Previously, a command could return the "Paylod" or the "Data", and each command was different. For example, `authorize` would return "data" but `getChannelPermissions` would return a "Payload".

```ts
// V1

// "Data" command
const {code} = await sdk.commands.authorize();

// "Payload" command
const {
cmd,
data: {permissions},
evt,
nonce,
} = await discordSdk.commands.getChannelPermissions();
```

The additional field of "Payload" commands are not useful for activity developers, and are used internally in the SDK. As such, we decided to make all commands "Data" commands, meaning in v2.0.0+ all commands simply return the desired data.

```ts
// V2

const {code} = await sdk.commands.authorize();
const {permissions} = await discordSdk.commands.getChannelPermissions();
```

An additional benefit of standardization of commands is better type inference. Previously, return value of the command was not obvious, and often referenced `zod`, a library used in the SDK to parse messages sent across the IPC bridge. We made it easier for typescript and activity developers to understand what any command does by annontating the commands with `zod`-less, straightforward types. Let's use `getChannelPermissions` as an example again.

```diff
- (property) getChannelPermissions: TPayloadCommand<Commands.GET_CHANNEL_PERMISSIONS, void, GetChannelPermissionsOutput> // v1
+ (property) getChannelPermissions: (args: void) => Promise<{ permissions: string | bigint;}> // v2
```

The v1 shape is undecipherable, and references other enums and shapes from within the SDK. It is not even clear that `getChannelPermissions` is a function.
The v2 shape, on the other hand, is quite clear. `getChannelPermissions` is an async function which returns an object containing attribute `permissions` which is either a `string` or a `bigint`.

# Step by Step Guide

Each activity is implemented differently, but migrating to v2 from v1 should be straightforward with the use of typescript. Here is a simple outline of how we expect the migration should occur.

1. Upgrade to 2.0.0 by updating `package.json` and installing (eg `yarn install`).
2. Run typescript (`tsc`) and observe errors. Most of the errors will be about the shape of commands, as expected.
3. Fix typescript errors. We expect most of not all typescript errors will be related to commands, and most of these will be fixed simply be removing `.data` from command responses (as now the response is the data) or changing the way the command response is deconstructed to simply deconstruct the data object.
4. Perform another audit of command usage within your activity, to catch those that are potentially not covered by typescript.
5. Perform a basic test of key functionality.
6. Profit from having standardized command shapes and better types from here on out!

## IFrame Playground Example migration

As a real example, our `iframe-playground` example application migrated to 2.0.0 from v1.11.0 and this is a summary of all the changes required.

```diff
examples/discord-activity-starter/packages/client/src/main.ts
const {data: selectedVoiceChannel} = await discordSdk.commands.getSelectedVoiceChannel();
+ const selectedVoiceChannel = await discordSdk.commands.getSelectedVoiceChannel();

examples/iframe-playground/packages/client/src/pages/CurrentGuild.tsx
- const {data} = await discordSdk.commands.getSelectedVoiceChannel();
- const guildId = data?.guild_id;
+ const channel = await discordSdk.commands.getSelectedVoiceChannel();
+ const guildId = channel?.guild_id;

examples/iframe-playground/packages/client/src/pages/EncourageHardwareAcceleration.tsx
- const {data} = await discordSdk.commands.encourageHardwareAcceleration();
- setHWAccEnabled(data?.enabled ?? null);
+ const {enabled} = await discordSdk.commands.encourageHardwareAcceleration();
+ setHWAccEnabled(enabled === true);

examples/iframe-playground/packages/client/src/pages/InAppPurchase.tsx
- const skusResp = await discordSdk.commands.getSkus();
- setRpcSkus(skusResp.data as Sku[]);
+ const skus = await discordSdk.commands.getSkus();
+ setRpcSkus(skus);

examples/iframe-playground/packages/client/src/pages/OpenInviteDialog.tsx
- const { data: {permissions} } = await discordSdk.commands.getChannelPermissions();
+ const {permissions} = await discordSdk.commands.getChannelPermissions();

examples/iframe-playground/packages/client/src/pages/PlatformBehaviors.tsx
- const {data} = await discordSdk.commands.getPlatformBehaviors();
- setPlatformBehaviors(data);
+ const behaviors = await discordSdk.commands.getPlatformBehaviors();
+ setPlatformBehaviors(behaviors);

examples/iframe-playground/packages/client/src/pages/VoiceSettings.tsx
- const voiceSettingsResp = await discordSdk.commands.getVoiceSettings();
- setVoiceSettings(voiceSettingsResp.data);
+ const voiceSettings = await discordSdk.commands.getVoiceSettings();
- setVoiceSettings(voiceSettings);
```
158 changes: 158 additions & 0 deletions docs/embedded-app-sdk-v1-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# embedded-app-sdk V1 Migration

If you were previously using activity-iframe-sdk, this guide is for you. Think of `@discord/embedded-app-sdk` as the next version of `activity-iframe-sdk` with a few breaking changes. This document is aimed to help developers migrate their activity code from activity-iframe-sdk v2 to @discord/embedded-app-sdk v1.0.0 and beyond. If you are upgrading from activity-iframe-sdk v1, please first reference [this document](/docs/activity-iframe-sdk-v2-migration.md)

## tldr;

Here's the high level list of changes

- [New SDK Name](#new-sdk-name)
- [Typescript support for subscribe and unsubscribe](#typescript-support-for-subscribe-and-unsubscribe)
- [initializeNetworkShims is different](#initializenetworkshims-is-different)
- [Removing unused commands, events, and types](#removing-unused-commands-events-and-types)

### New SDK Name

We've renamed the SDK to align with the long-term vision for Discord apps. If you're embedding an app inside of Discord, this SDK has got you covered. The main use case today is for Discord apps which launch activities via iframes, however, as this name implies, this SDK could support use for Discord applications beyond just activities.

### Typescript support for subscribe and unsubscribe

Previously, there were no typescript types provided when subscribing to events. Now, as soon as you provide an event name, typescript can help infer the shape of data returned by the callback, as well as additional subscribeArgs, when necessary.

Often, you may not want to describe the subscription callback inline. If you are using typescript, you can define the event shape outside of the callback, using the generic type `EventPayloadData`. Check out this example where we subscribe to the `SPEAKING_START` event.

```ts
import {DiscordSDK, EventPayloadData} from '@discord/embedded-app-sdk';

// Initialize the SDK
const discordSdk = new DiscordSDK(CLIENT_ID);

// Retrieve the typescript type for the SPEAKING_START event
type SPEAKING_START_EVENT = EventPayloadData<'SPEAKING_START'>;

// Define the callback function that will fire whenever a SPEAKING_START event is captured
function handleStartTalking(event: SPEAKING_START_EVENT) {
console.log(`user ${event.user_id} started talking`);
}

// Subscribe to the SPEAKING_START event and pass appropriate
discordSdk.subscribe('SPEAKING_START', handleStartTalking, {channel_id: discordSdk.channelId});
```

### initializeNetworkShims is different

If you're using `initializeNetworkShims` in your activity, you will need to migrate it to the new API `patchUrlMappings`. It's the same in spirit, but it has a few differences:

- We have renamed `initializeNetworkShims` to `patchUrlMappings`
- `patchUrlMappings` is now exported as a utility separate from the SDK. This allows you to initialize shims as soon as possible, seperate from SDK initialization
- `patchUrlMappings` now includes new functionality that can update html `src` attributes to adjust to your url mappings.
- `patchUrlMappings` now includes a second argument where you can set specific configuration for each "shim". See usage in the example below

```ts
import {patchUrlMappings} from '@discord/embedded-app-sdk';

patchUrlMappings(
[
{
target: 'google.com',
prefix: '/google',
},
],
{
patchFetch: true, // Defaults to true
patchWebSocket: true, // Defaults to true
patchXhr: true, // Defaults to true
patchSrcAttributes: true, // Defaults to false (potentially compute expensive for your UI)
}
);
```

### Removing unused commands and events

The original SDK included several events and commands which are either no longer relevant, unusable, or otherwise not something we want in the SDK.

The following were removed or modified:

- Removed Events
- `ACTIVITY_JOIN`
- If you are using this today, we encourage subscribing to the `ACTIVITY_INSTANCE_PARTICIPANTS_UPDATE` event instead
- `ACTIVITY_SPECTATE`
- If you are using this today, we encourage subscribing to the `ACTIVITY_INSTANCE_PARTICIPANTS_UPDATE` event instead
- `ACTIVITY_PIP_MODE_UPDATE`
- If you are using this today, we encourage subscribing to the `ACTIVITY_LAYOUT_MODE_UPDATE` event instead
- `CAPTURE_SHORTCUT_CHANGE`
- `GUILD_STATUS`
- `GUILD_CREATE`
- `CHANNEL_CREATE`
- `VOICE_CHANNEL_CREATE`
- `NOTIFICATION_CREATE`
- `ACTIVITY_JOIN_REQUEST`
- `VOICE_STATE_CREATE`
- `VOICE_STATE_DELETE`
- `VOICE_SETTINGS_UPDATE`
- `VOICE_CONNECTION_STATUS`
- Removed Commands
- `getSelectedVoiceChannel`
We recommend using `getChannel` instead, where appropriate. If you need access to the current channel's id, it can be accessed immediately by the SDK, directly at `discordSdk.channelId`
- `subscribeToLayoutModeUpdatesCompat` and `unsubscribeFromLayoutModeUpdatesCompat`
- If you are using these today, we encourage subscribing to the `ACTIVITY_LAYOUT_MODE_UPDATE` event instead
- `setUserVoiceSettings`
- `setVoiceSettings`
- `getVoiceSettings`
- `startPremiumPurchase`

# Step by Step Guide

Each activity is implemented differently, but migrating to embedded-app-sdk from activity-iframe-sdk should be straightforward with the use of typescript. Here is a simple outline of how we expect the migration should occur.

0. If you are upgrading from `activity-iframe-sdk` v1, please review and implement the [activity-iframe-sdk v2 migration guide](/docs/activity-iframe-sdk-v2-migration.md) before following these steps.
1. Upgrade to 1.0.0 by updating `package.json` and installing (eg `npm install @discord/embedded-app-sdk`).
2. Remove any code related to installing a private github package. This will most likely look like removing a `.npmrc` file or other references to `npm.pkg.github.com`
3. Run typescript (`tsc`) and observe errors. Most of the errors will be related to usage of `subscribe`, or usage of removed commands and events, as expected.
4. Fix typescript errors. We expect most of not all typescript errors will be related to `subscribe`. The type `EventPayloadData<'YOUR_EVENT'>` will be your biggest helper here. Remove usage of unsupported events and command, and migrate as necessary, per recommandations [above](#removing-unused-commands-and-events)
5. Perform a basic test of key functionality.
6. Profit from having type safety and the latest SDK updates from here on out!

## IFrame Playground Example migration

As a real example, our `iframe-playground` example application migrated to @discord/embedded-app-sdk 1.0.0 from @discord/activity-iframe-sdk. This is a summary of all the changes required.

### Migrate subscribeToLayoutModeUpdatesCompat command to instead subscribe to the ACTIVITY_LAYOUT_MODE_UPDATE event

```diff
examples/iframe-playground/packages/client/src/pages/LayoutMode.tsx
- import {Common} from '@discord/activity-iframe-sdk';
+ import {Common, EventPayloadData} from '@discord/embedded-app-sdk';

- const handleLayoutModeUpdate = React.useCallback((update: {layout_mode: number}) => {
+ const handleLayoutModeUpdate = React.useCallback((update: EventPayloadData<'ACTIVITY_LAYOUT_MODE_UPDATE'>) => {

React.useEffect(() => {
- discordSdk.subscribeToLayoutModeUpdatesCompat(handleLayoutModeUpdate);
+ discordSdk.subscribe('ACTIVITY_LAYOUT_MODE_UPDATE', handleLayoutModeUpdate);
return () => {
- discordSdk.unsubscribeFromLayoutModeUpdatesCompat(handleLayoutModeUpdate);
+ discordSdk.unsubscribe('ACTIVITY_LAYOUT_MODE_UPDATE', handleLayoutModeUpdate);
```

### Migrate event subscriptions to define event shape

```diff
examples/iframe-playground/packages/client/src/pages/Instance.tsx
- import {Events, Types} from '@discord/activity-iframe-sdk';
+ import {Events, EventPayloadData} from '@discord/embedded-app-sdk';

- type UpdateEvent = Types.GetActivityInstanceConnectedParticipantsResponse;
+ type UpdateEvent = EventPayloadData<'ACTIVITY_INSTANCE_PARTICIPANTS_UPDATE'>;
```

### Migrate usage of initializeNetworkShims to patchUrlMappings

```diff

- import {DiscordSDK, DiscordSDKMock, IDiscordSDK} from '@discord/embedded-app-sdk';
+ import {DiscordSDK, DiscordSDKMock, IDiscordSDK, patchUrlMappings} from '@discord/embedded-app-sdk';

- discordSdk.initializeNetworkShims(mappings);
+ patchUrlMappings(mappings);
```

0 comments on commit bbae67b

Please sign in to comment.