From a119c8187db9b0e08403936aa8c663baeb5c48d1 Mon Sep 17 00:00:00 2001 From: Alex Ni <12097569+nialexsan@users.noreply.github.com> Date: Tue, 8 Aug 2023 18:26:09 -0400 Subject: [PATCH 1/4] add DEEPLINK/RPC spec --- application/20221108-fcl-specification.md | 97 +++++++++++++++++++++-- 1 file changed, 90 insertions(+), 7 deletions(-) diff --git a/application/20221108-fcl-specification.md b/application/20221108-fcl-specification.md index 03f1f535..e9ba72bb 100644 --- a/application/20221108-fcl-specification.md +++ b/application/20221108-fcl-specification.md @@ -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).** #### FCL Objects @@ -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: @@ -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). ##### `PreSignable` @@ -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) @@ -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 | | -------------- | ----- | ---- | @@ -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. @@ -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) @@ -695,6 +703,81 @@ 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) + +##### 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"
config({
#emsp;"discovery.authn.endpoint": "https://wallet.com/api/discovery",
#emsp;"app.detail.title": "My Awesome App",
#emsp;"app.detail.icon": "https://app.com/assets/icon.jpg",
}) + + App/FCL ->> Wallet/API: http POST https://wallet.com/api/discovery + activate Wallet/API + note right of App/FCL: BODY {
#emsp;"supportedStrategies": [
#emsp;#emsp;"HTTP/POST",
#emsp;#emsp;"DEEPLINK/RPC"
#emsp;],
#emsp;...BodyType,
} + Wallet/API ->> App/FCL: POST response with available services + deactivate Wallet/API + note left of Wallet/API: [{
#emsp;"f_type": "Service",#emsp;
#emsp;"f_vsn": "1.0.0",
#emsp;"type": ServiceType,
#emsp;"method": "DEEPLINK/RPC",
#emsp;"endpoint": "https://wallet.com/_A_",
#emsp;"data": { "foo": "bar" },
#emsp;"params": { "omg": "rawr" },
},...] + App/FCL ->> Mobile Browser: http GET https://wallet.com/_A_ + activate Mobile Browser + note right of App/FCL: URLSearchParams {
#emsp;"fclMessageJson": `${JSON.Stringify({...body, config})}`},
#emsp;"fcl_redirect_url": appDeeplinkUrl,
#emsp;...
} + alt Approved + Mobile Browser->>App/FCL: Redirect to appDeeplinkUrl + note left of Mobile Browser: URLSearchParams {
#emsp;"fclResponseJson": {
#emsp;#emsp;"status": "APPROVED",
#emsp;#emsp;"data": ReturnValue,
#emsp;}
} + else Declined + Mobile Browser->>App/FCL: Redirect to appDeeplinkUrl + note left of Mobile Browser: URLSearchParams {
#emsp;"fclResponseJson": {
#emsp;#emsp;"status": "DECLINED",
#emsp;#emsp;"reason": "The user didn't want to do it ...",
#emsp;}
} + end + deactivate Mobile Browser + +``` + #### Service Method Plugins TODO From 94056e36d8a5c08569159167ca4cbbabb9a2ae89 Mon Sep 17 00:00:00 2001 From: Alex Ni <12097569+nialexsan@users.noreply.github.com> Date: Wed, 9 Aug 2023 08:28:26 -0400 Subject: [PATCH 2/4] add discovery endpoint details --- application/20221108-fcl-specification.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/application/20221108-fcl-specification.md b/application/20221108-fcl-specification.md index e9ba72bb..b7f96a3f 100644 --- a/application/20221108-fcl-specification.md +++ b/application/20221108-fcl-specification.md @@ -778,6 +778,8 @@ sequenceDiagram ``` +*NOTE: react-native implementation needs `discovery.authn.endpoint` config to point to be defined. See below.* + #### Service Method Plugins TODO @@ -818,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: From 24248d8461cf43608a5201c36964865a385fe808 Mon Sep 17 00:00:00 2001 From: Alex Ni <12097569+nialexsan@users.noreply.github.com> Date: Wed, 9 Aug 2023 08:32:04 -0400 Subject: [PATCH 3/4] restore punctuation --- application/20221108-fcl-specification.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/application/20221108-fcl-specification.md b/application/20221108-fcl-specification.md index b7f96a3f..daa2d858 100644 --- a/application/20221108-fcl-specification.md +++ b/application/20221108-fcl-specification.md @@ -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`, `method: "EXT/RPC"`, and `method: "DEEPLINK/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 | | -------------- | ----- | ---- | @@ -650,7 +650,7 @@ This optional feature is the ability for FCL to render an iframe, popup, new tab 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)* +*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) @@ -708,12 +708,12 @@ chrome.tabs.sendMessage(tabs[0].id, { `DEEPLINK/RPC` works somewhat similar to `IFRAME/RPC`: -- A mobile browser is opened +- 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 + - 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. +- 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 From ff029e58bf3a2f86d08d275a9a1e767dbde6c58e Mon Sep 17 00:00:00 2001 From: Alex Ni <12097569+nialexsan@users.noreply.github.com> Date: Wed, 9 Aug 2023 12:09:40 -0400 Subject: [PATCH 4/4] extend active block --- application/20221108-fcl-specification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/20221108-fcl-specification.md b/application/20221108-fcl-specification.md index daa2d858..d721e6f4 100644 --- a/application/20221108-fcl-specification.md +++ b/application/20221108-fcl-specification.md @@ -762,8 +762,8 @@ sequenceDiagram activate Wallet/API note right of App/FCL: BODY {
#emsp;"supportedStrategies": [
#emsp;#emsp;"HTTP/POST",
#emsp;#emsp;"DEEPLINK/RPC"
#emsp;],
#emsp;...BodyType,
} Wallet/API ->> App/FCL: POST response with available services - deactivate Wallet/API note left of Wallet/API: [{
#emsp;"f_type": "Service",#emsp;
#emsp;"f_vsn": "1.0.0",
#emsp;"type": ServiceType,
#emsp;"method": "DEEPLINK/RPC",
#emsp;"endpoint": "https://wallet.com/_A_",
#emsp;"data": { "foo": "bar" },
#emsp;"params": { "omg": "rawr" },
},...] + deactivate Wallet/API App/FCL ->> Mobile Browser: http GET https://wallet.com/_A_ activate Mobile Browser note right of App/FCL: URLSearchParams {
#emsp;"fclMessageJson": `${JSON.Stringify({...body, config})}`},
#emsp;"fcl_redirect_url": appDeeplinkUrl,
#emsp;...
}