-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
273527f
commit 9e166b6
Showing
2 changed files
with
178 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
177 changes: 177 additions & 0 deletions
177
src/docs/getting-started/subscribe-to-active-account.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
# Subscribe to `ACTIVE_ACCOUNT_SET` Advanced Example | ||
|
||
:::warning | ||
This page is still under work. Some sections may be incomplete or subject to change. | ||
::: | ||
|
||
Beacon provides developers the ability to subscribe to its internal state, as shown on the dedicated page. Since version 4.2.0, subscribing to `ACTIVE_ACCOUNT_SET` has become mandatory. This page provides a custom example with user validation by requesting the wallet to sign a payload. | ||
|
||
## Before Starting | ||
|
||
Be aware that calling one of the functions listed below will also trigger `ACTIVE_ACCOUNT_SET`. Be careful when calling such functions inside the handler to avoid causing your dApp to enter an endless loop. | ||
|
||
**List of functions:** | ||
|
||
- `requestPermissions` | ||
- `setActiveAccount` | ||
- `clearActiveAccount` | ||
- `disconnect` | ||
- `destroy` | ||
- `removeAccount` | ||
- `removeAllAccounts` | ||
|
||
## Example | ||
|
||
After initializing your `dAppClient` instance, you need to subscribe to `ACTIVE_ACCOUNT_SET` as shown below: | ||
|
||
```ts | ||
const dAppClient = new DAppClient({ | ||
name: "Beacon Docs", | ||
}); | ||
|
||
dAppClient.subscribeToEvent(BeaconEvent.ACTIVE_ACCOUNT_SET, (account) => { | ||
console.log(`${BeaconEvent.ACTIVE_ACCOUNT_SET} triggered: `, account); | ||
}); | ||
``` | ||
|
||
The handler should be as concise as possible. Ideally, it should just update your active account globally throughout your dApp. However, in some cases, adding extra lines of code can be beneficial as shown below. | ||
|
||
## Adding `requestSignPayload` | ||
|
||
Sometimes `requestPermissions` may not be enough, and you want to ensure the user who has synced with the wallet is authorized. A common way to accomplish this is by sending a _sign_payload_ request to the wallet. | ||
|
||
```ts | ||
dAppClient.subscribeToEvent(BeaconEvent.ACTIVE_ACCOUNT_SET, async (account) => { | ||
console.log( | ||
`${BeaconEvent.ACTIVE_ACCOUNT_SET} triggered: `, | ||
account?.address, | ||
); | ||
|
||
if (!account) { | ||
return; | ||
} | ||
|
||
try { | ||
await dAppClient.requestSignPayload({ | ||
payload: | ||
"05010000004254657a6f73205369676e6564204d6573736167653a207465737455726c20323032332d30322d30385431303a33363a31382e3435345a2048656c6c6f20576f726c64", | ||
}); | ||
} catch (err) { | ||
// The request was rejected | ||
// Abort | ||
} | ||
}); | ||
``` | ||
|
||
> **Note:** `ACTIVE_ACCOUNT_SET` gets triggered both when setting a new account and resetting the current one. Make sure not to send a **sign_payload** request without an account. | ||
## Multitab Synchronization | ||
|
||
While the example above works for single-page dApps, it may become problematic in a multi-tab setup. Beacon emits an event to keep each tab synced with the internal state. Therefore, if your dApp needs multiple tabs support, the above approach may cause issues. Each tab will send a **sign_payload** request to the wallet, which is not intended and may lead to request rejection if a certain threshold is reached. | ||
|
||
To address this, we need to implement multi-tab synchronization. There are multiple ways to achieve this; for simplicity, we use `broadcast-channel`. | ||
|
||
### Step 1: Install `broadcast-channel` | ||
|
||
Run the following command: | ||
|
||
```bash | ||
npm install broadcast-channel | ||
``` | ||
|
||
### Step 2: Set Up the Channel | ||
|
||
The main idea is to elect a tab as the Leader so that only this tab will send a request to the wallet. First, set up a channel. | ||
|
||
```ts | ||
const channel = new BroadcastChannel("beacon-channel"); | ||
const elector = createLeaderElection(channel); | ||
``` | ||
|
||
Check if a leader already exists, otherwise request leadership. | ||
|
||
```ts | ||
elector.hasLeader().then(async (hasLeader) => { | ||
if (!hasLeader) { | ||
await elector.awaitLeadership(); | ||
} | ||
}); | ||
``` | ||
|
||
### Step 3: Update the Handler | ||
|
||
Now, inside the handler, check whether the current tab has the leadership. If not, do not send a `sign_payload` request. | ||
|
||
```ts | ||
dAppClient.subscribeToEvent(BeaconEvent.ACTIVE_ACCOUNT_SET, async (account) => { | ||
console.log( | ||
`${BeaconEvent.ACTIVE_ACCOUNT_SET} triggered: `, | ||
account?.address, | ||
); | ||
|
||
if (!account || !elector.isLeader) { | ||
return; | ||
} | ||
|
||
try { | ||
await dAppClient.requestSignPayload({ | ||
payload: | ||
"05010000004254657a6f73205369676e6564204d6573736167653a207465737455726c20323032332d30322d30385431303a33363a31382e3435345a2048656c6c6f20576f726c64", | ||
}); | ||
} catch (err) { | ||
// The request was rejected | ||
// Abort | ||
} | ||
}); | ||
``` | ||
|
||
## Conclusion | ||
|
||
The end result should look like this: | ||
|
||
```ts | ||
import { DAppClient, BeaconEvent } from "@airgap/beacon-dapp"; | ||
import { NetworkType } from "@airgap/beacon-types"; | ||
import { BroadcastChannel, createLeaderElection } from "broadcast-channel"; | ||
|
||
const channel = new BroadcastChannel("beacon-test"); | ||
const elector = createLeaderElection(channel); | ||
|
||
elector.hasLeader().then(async (hasLeader) => { | ||
if (!hasLeader) { | ||
await elector.awaitLeadership(); | ||
} | ||
}); | ||
|
||
const dAppClient = new DAppClient({ | ||
name: "Beacon Playground", | ||
network: { | ||
type: NetworkType.GHOSTNET, | ||
}, | ||
}); | ||
|
||
dAppClient.subscribeToEvent(BeaconEvent.ACTIVE_ACCOUNT_SET, async (account) => { | ||
console.log( | ||
`${BeaconEvent.ACTIVE_ACCOUNT_SET} triggered: `, | ||
account?.address, | ||
); | ||
|
||
if (!account || !elector.isLeader) { | ||
return; | ||
} | ||
|
||
try { | ||
await dAppClient.requestSignPayload({ | ||
payload: | ||
"05010000004254657a6f73205369676e6564204d6573736167653a207465737455726c20323032332d30322d30385431303a33363a31382e3435345a2048656c6f20576f726c64", | ||
}); | ||
} catch (err) { | ||
// The request was rejected | ||
// Abort | ||
} | ||
}); | ||
``` | ||
|
||
## Live Example | ||
|
||
For a live example, check [here](https://stackblitz.com/edit/vitejs-vite-71wsul-l2duvv?file=index.ts). We recommend cloning the repository locally for a better development experience. |