Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add DEEPLINK/RPC spec #129

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 95 additions & 7 deletions application/20221108-fcl-specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ In this section we define the schema of objects used in the protocol. While they

For the schema definition language we choose TypeScript, so that the schema closely resembles the actual type definitions one would use when making an FCL implementation.

**Note that currently there are no official type definitions available for FCL. If you are using TypeScript, you will have to create your own type definitions (possibly based on the schema definitions presented in this document).**
**Note that currently the official type definitions available for FCL are not fully implemented. If you are using TypeScript, you will have to create your own type definitions for some structures (possibly based on the schema definitions presented in this document).**

#### <a id="fclobjects"></a> FCL Objects

Expand Down Expand Up @@ -283,7 +283,7 @@ Each response back to FCL must be "wrapped" in a `PollingResponse`. The `status`

In summary, zero or more `PENDING` responses should be followed by a non-pending response. It is entirely acceptable for your service to immediately return an `APPROVED` Polling Response, skipping a `PENDING` state.

See also [PollingResponse](https://github.com/onflow/flow-js-sdk/blob/master/packages/fcl/src/current-user/normalize/polling-response.js).
See also [PollingResponse](https://github.com/onflow/fcl-js/blob/master/packages/fcl/src/normalizers/service/polling-response.js).

Here are some examples of valid `PollingResponse` objects:

Expand Down Expand Up @@ -466,7 +466,7 @@ interface CompositeSignature extends ObjectBase {
}
```

See also [CompositeSignature](https://github.com/onflow/flow-js-sdk/blob/master/packages/fcl/src/current-user/normalize/composite-signature.js).
See also [CompositeSignature](https://github.com/onflow/fcl-js/tree/master/packages/fcl/src/normalizers/service/composite-signature.js).

##### <a id="presignable"></a> `PreSignable`

Expand Down Expand Up @@ -548,12 +548,12 @@ https://github.com/onflow/fcl-js/blob/master/packages/fcl/src/normalizers/servic
##### `frame`

TODO
[frame](https://github.com/onflow/flow-js-sdk/blob/master/packages/fcl/src/current-user/normalize/frame.js)
[frame](https://github.com/onflow/fcl-js/tree/master/packages/fcl/src/normalizers/service/frame.js)

##### `local-view`

TODO
[local-view](https://github.com/onflow/flow-js-sdk/blob/master/packages/fcl/src/current-user/normalize/local-view.js)
[local-view](https://github.com/onflow/fcl-js/tree/master/packages/fcl/src/normalizers/service/local-view.js)

[FCL Normalizers](https://github.com/onflow/fcl-js/tree/master/packages/fcl/src/normalizers/service)

Expand All @@ -575,7 +575,7 @@ Ultimately we want to do this back and forth via a secure back-channel (https re

Where possible, you should aim to provide a back-channel support for services, and only fall back to a front-channel if absolutely necessary.

Back-channel communications use `method: "HTTP/POST"`, while front-channel communications use `method: "IFRAME/RPC"`, `method: "POP/RPC"`, `method: "TAB/RPC` and `method: "EXT/RPC"`.
Back-channel communications use `method: "HTTP/POST"`, while front-channel communications use `method: "IFRAME/RPC"`, `method: "POP/RPC"`, `method: "TAB/RPC`, `method: "EXT/RPC"`, and `method: "DEEPLINK/RPC"`.

| Service Method | Front | Back |
| -------------- | ----- | ---- |
Expand All @@ -584,6 +584,7 @@ Back-channel communications use `method: "HTTP/POST"`, while front-channel commu
| POP/RPC | ✅ | ⛔ |
| TAB/RPC | ✅ | ⛔ |
| EXT/RPC | ✅ | ⛔ |
| DEEPLINK/RPC | ✅ | ⛔ |

It's important to note that regardless of the method of communication, the data that is sent back and forth between the parties involved is the same.

Expand Down Expand Up @@ -643,7 +644,14 @@ FCL will use that `BackChannelRpc` to request a new `PollingResponse` which itse
If it is `APPROVED` FCL will return, otherwise if it is `DECLINED` FCL will error. However, if it is `PENDING`, it will use the `BackChannelRpc` supplied in the new `PollingResponse` updates field. It will repeat this cycle until it is either `APPROVED` or `DECLINED`.

There is an additional optional feature that `HTTP/POST` enables in the first `PollingResponse` that is returned.
This optional feature is the ability for FCL to render an iframe, popup or new tab, and it can be triggered by supplying a service `type: "VIEW/IFRAME"`, `type: "VIEW/POP"` or `type: "VIEW/TAB"` and the `endpoint` that the wallet wishes to render in the `local` field of the `PollingResponse`. This is a great way for a wallet provider to switch to a webpage if displaying a UI is necessary for the service it is performing.
This optional feature is the ability for FCL to render an iframe, popup, new tab or to open a mobile browser, and it can be triggered by supplying a service `type: "VIEW/IFRAME"`, `type: "VIEW/POP"`, `type: "VIEW/TAB"`, or `type: "VIEW/MOBILE_BROWSER"` and the `endpoint` that the wallet wishes to render in the `local` field of the `PollingResponse`. This is a great way for a wallet provider to switch to a webpage if displaying a UI is necessary for the service it is performing.

###### VIEW/MOBILE_BROWSER

Specifically for mobile platforms there is a service `type: "VIEW/MOBILE_BROWSER"`. It works almost the same way as `type: "VIEW/IFRAME"` with an exception that it doesn't communicate back to the app as it doesn't have `window.postMessage` method.

*NOTE: You also need to call `window.close()` when you finish processing the request on the page specifically for Android, as it is NOT possible to control the mobile browser window from the app after openning it (you cannot close the mobile browser from the app).*


![HTTP/POST Diagram](https://raw.githubusercontent.com/onflow/flow-js-sdk/master/packages/fcl/assets/service-method-diagrams/http-post.png)

Expand Down Expand Up @@ -695,6 +703,83 @@ chrome.tabs.sendMessage(tabs[0].id, {

![EXT/RPC Diagram](https://raw.githubusercontent.com/onflow/flow-js-sdk/master/packages/fcl/assets/service-method-diagrams/ext-rpc.png)


##### <a id="deeplinkrpc"></a> DEEPLINK/RPC (Front Channel)

`DEEPLINK/RPC` works somewhat similar to `IFRAME/RPC`:

- A mobile browser is opened:
- URL comes from the `endpoint` in the service
- message `body` and `config` are encoded (`JSON.stringify`) in `fclMessageJson` URL param;
- The browser handler attaches an event listener to the browser object (iOS) or listense to deeplink changes (Android)
- The wallet sends back an `"APPROVED"` or `"DECLINED"` message. (It should redirect back to the application using a deeplink redirect url passed in `fcl_redirect_url` and the redirect url should contain `fclResponseJson` URL param with stringified data object). This can be simplified using `WalletUtils.approve` and `WalletUtils.decline`,
- If it's approved, the response's data field will need to be what FCL is expecting,
- If it's declined, the response's reason field should say why it was declined.

```javascript
export const WalletUtils.approve = data => {
sendMsgToFCL("FCL:VIEW:RESPONSE", {
f_vsn: "1.0.0",
status: "APPROVED",
reason: null,
data: data,
})
}

export const WalletUtils.decline = reason => {
sendMsgToFCL("FCL:VIEW:RESPONSE", {
f_vsn: "1.0.0",
status: "DECLINED",
reason: reason,
data: null,
})
}
```

Redirect happen automatically with `sendMsgToFCL` call if you're using standard FCL Wallet Utils, as it checks `fcl_redirect_url` in URL params

Unlike `IFRAME/RPC` this strategy cannot provide non-terminal status updates (`PENDING` and `REDIRECT`) from the wallet back to the application as any `sendMsgToFCL` call will cause a Deeplink redirect back to the application.

ServiceTypes, BodyTypes and Expected ReturnValues
| ServiceType | BodyType | ReturnValue
|---------------:|-------------|----------------------
| authn | --- | AuthnResponse
| authz | Signable | CompositeSignature
| pre-authz | PreSignable | PreAuthzResponse
| user-signature | Signable | [CompositeSignature]



```mermaid
%%{init: { 'sequence': {'noteAlign': 'left'} }}%%
sequenceDiagram
participant App/FCL
participant Wallet/API
participant Mobile Browser
note over App/FCL: import { config } from "@onflow/fcl"<br/>config({<br/>#emsp;"discovery.authn.endpoint": "https://wallet.com/api/discovery",<br/>#emsp;"app.detail.title": "My Awesome App",<br/>#emsp;"app.detail.icon": "https://app.com/assets/icon.jpg",<br/>})

App/FCL ->> Wallet/API: http POST https://wallet.com/api/discovery
activate Wallet/API
note right of App/FCL: BODY {<br/>#emsp;"supportedStrategies": [<br/>#emsp;#emsp;"HTTP/POST",<br/>#emsp;#emsp;"DEEPLINK/RPC"<br/>#emsp;],<br/>#emsp;...BodyType,<br/>}
Wallet/API ->> App/FCL: POST response with available services
note left of Wallet/API: [{<br/>#emsp;"f_type": "Service",#emsp;<br/>#emsp;"f_vsn": "1.0.0",<br/>#emsp;"type": ServiceType,<br/>#emsp;"method": "DEEPLINK/RPC",<br/>#emsp;"endpoint": "https://wallet.com/_A_",<br/>#emsp;"data": { "foo": "bar" },<br/>#emsp;"params": { "omg": "rawr" },<br/>},...]
deactivate Wallet/API
App/FCL ->> Mobile Browser: http GET https://wallet.com/_A_
activate Mobile Browser
note right of App/FCL: URLSearchParams {<br/>#emsp;"fclMessageJson": `${JSON.Stringify({...body, config})}`},<br/>#emsp;"fcl_redirect_url": appDeeplinkUrl,<br/>#emsp;...<br/>}
alt Approved
Mobile Browser->>App/FCL: Redirect to appDeeplinkUrl
note left of Mobile Browser: URLSearchParams {<br/>#emsp;"fclResponseJson": {<br/>#emsp;#emsp;"status": "APPROVED",<br/>#emsp;#emsp;"data": ReturnValue,<br/>#emsp;}<br/>}
else Declined
Mobile Browser->>App/FCL: Redirect to appDeeplinkUrl
note left of Mobile Browser: URLSearchParams {<br/>#emsp;"fclResponseJson": {<br/>#emsp;#emsp;"status": "DECLINED",<br/>#emsp;#emsp;"reason": "The user didn't want to do it ...",<br/>#emsp;}<br/>}
end
deactivate Mobile Browser

```

*NOTE: react-native implementation needs `discovery.authn.endpoint` config to point to be defined. See below.*

#### <a id="servicemethodplugins"></a> Service Method Plugins

TODO
Expand Down Expand Up @@ -735,6 +820,9 @@ config({

If the method specified is `IFRAME/RPC`, `POP/RPC` or `TAB/RPC`, then the URL specified as `discovery.wallet` will be rendered as a webpage. If the configured method is `EXT/RPC`, `discovery.wallet` should be set to the extension's `authn` `endpoint`. Otherwise, if the method specified is `HTTP/POST`, then the authentication process will happen over HTTP requests. (While authentication can be accomplished using any of those service methods, this example will use the `IFRAME/RPC` service method.)

For `DEEPLINK/RPC` method it's required to specify `discovery.authn.endpoint` as an API endpoint, that should return an array of Authn Service with method
These services could be consumed by `useServiceDiscovery` react hook.

Once the Authentication webpage is rendered, the extension popup is enabled, or the API is ready, you then need to tell FCL that it is ready. You will do this by sending a message to FCL, and FCL will send back a message with some additional information that you can use about the application requesting authentication on behalf of the user.

The following example is using the `IFRAME/RPC` method. Your authentication webpage will likely resemble the following code:
Expand Down