defmodule WeChat.Storage.Adapter.DefaultClient do
@moduledoc """
For `common` application.
```
defmodule MyClient do
use WeChat,
adapter_storage: {:default, "http://localhost:4000"},
appid: "MyAppID"
end
```
The above equals as below
```
defmodule MyClient do
use WeChat,
adapter_storage: {
WeChat.Storage.Adapter.DefaultClient,
"http://localhost:4000"
},
appid: "MyAppID"
end
```
When uses the `{:default, "MyHubBaseURL"}`, there requires some HTTP API functions are provided by the hub web app server,
let's take "MyHubBaseURL" as "http://localhost:4000" for example.
### Refresh access token
```
Request
- method: POST http://localhost:4000/refresh/access_token
- body: %{"appid" => "MyAppID", "access_token" => "CurrentExpiryAccessToken"}
- body format: json
Response
- success body: %{"access_token" => "...", "expires_in" => integer(), "timestamp" => integer()}
- error body: any content is acceptable will be return back into `WeChat.Error`
```
### Fetch access token
```
Request
- method: GET http://localhost:4000/client/access_token
- query string: appid="MyAppID"
Response
- success body: %{"access_token" => "...", "expires_in" => integer(), "timestamp" => integer()}
- error body: any error content is acceptable will be return back into `WeChat.Error`
```
### Fetch jsapi-ticket/card-ticket
```
Request
- method: GET http://localhost:4000/client/ticket
- query string: appid="MyAppID"&type=jsapi or appid="MyAppID"&type=wx_card
Response
- success body: %{"ticket" => "...", "expires_in" => integer(), timestamp => integer(), "type" => "jsapi" | "wx_card"}
- error body: any error content is acceptable will be return back into `WeChat.Error`
```
"""
@behaviour WeChat.Storage.Client
use WeChat.Registry
alias WeChat.Storage.DefaultHubConnector, as: Connector
alias WeChat.Error
@impl true
@decorate cache()
def fetch_access_token(appid, hub_base_url) do
Connector.fetch_access_token(appid, hub_base_url)
end
@impl true
@decorate cache()
def refresh_access_token(appid, access_token, hub_base_url) do
Connector.refresh_access_token(appid, access_token, hub_base_url)
end
@impl true
@decorate cache()
def fetch_ticket(appid, type, hub_base_url) do
# currently only support `type` as "jsapi" | "wx_card"
Connector.fetch_ticket(appid, type, hub_base_url)
end
end
defmodule WeChat.Storage.Adapter.DefaultComponentClient do
@moduledoc """
For `component` application.
```
defmodule MyClient do
use WeChat.Component,
adapter_storage: {:default, "http://localhost:4000"},
appid: "MyComponentAppID"
end
```
The above equals as below
```
defmodule MyClient do
use WeChat.Component,
adapter_storage: {
WeChat.Storage.Adapter.DefaultComponentClient,
"http://localhost:4000"
},
appid: "MyComponentAppID"
end
```
When uses the `{:default, "MyHubBaseURL"}`, there requires some HTTP API functions are provided by the hub web app server,
let's take "MyHubBaseURL" as "http://localhost:4000" for example.
### Refresh authorizer access_token
```
Request
- method: POST http://localhost:4000/refresh/access_token
- body: %{"appid" => "Your3rdComponentAppID", "authorizer_appid" => "YourAuthorizerAppID", "access_token" => "CurrentExpiryAccessToken"}
- body format: json
Response
- success body: %{"access_token" => "...", "expires_in" => integer(), "timestamp" => integer()}
- error body: any content is acceptable will be return back into `WeChat.Error`
```
### Fetch access_token
```
Request
- method: GET http://localhost:4000/client/access_token
- query string: appid="MyAppID"
Response
- success body: %{"access_token" => "...", "expires_in" => integer(), "timestamp" => integer()}
- error body: any error content is acceptable will be return back into `WeChat.Error`
```
### Fetch jsapi-ticket/card-ticket
```
Request
- method: GET http://localhost:4000/client/ticket
- query string: appid="MyAppID"&type=jsapi or appid="MyAppID"&type=wx_card
Response
- success body: %{"ticket" => "...", "expires_in" => integer(), "timestamp" => integer(), "type" => "jsapi" | "wx_card"}
- error body: any error content is acceptable will be return back into `WeChat.Error`
```
"""
@behaviour WeChat.Storage.ComponentClient
use WeChat.Registry
alias WeChat.Storage.DefaultHubConnector, as: Connector
alias WeChat.Error
@impl true
@decorate cache()
def fetch_access_token(appid, authorizer_appid, hub_base_url) do
Connector.fetch_access_token(appid, authorizer_appid, hub_base_url)
end
@impl true
@decorate cache()
def fetch_component_access_token(appid, hub_base_url) do
Connector.fetch_component_access_token(appid, hub_base_url)
end
@impl true
@decorate cache()
def refresh_access_token(
appid,
authorizer_appid,
access_token,
hub_base_url
) do
Connector.refresh_access_token(appid, authorizer_appid, access_token, hub_base_url)
end
@impl true
@decorate cache()
def refresh_component_access_token(appid, component_access_token, hub_base_url) do
Connector.refresh_component_access_token(appid, component_access_token, hub_base_url)
end
@impl true
@decorate cache()
def fetch_ticket(appid, authorizer_appid, type, hub_base_url) do
# Currently, `type` supports "jsapi" | "wx_card"
Connector.fetch_ticket(appid, authorizer_appid, type, hub_base_url)
end
end
defmodule WeChat.Storage.DefaultHubConnector do
@moduledoc false
require Logger
alias WeChat.{Error, Url}
def refresh_access_token(appid, access_token, hub_base_url) do
Logger.info(
"send refresh_token request to WeChat hub for appid: #{inspect(appid)}, access_token: #{inspect(access_token)}"
)
token =
hub_base_url
|> client()
|> Tesla.post(Url.to_refresh_access_token(), %{appid: appid, access_token: access_token})
|> response_to_access_token()
Logger.info(
"received refreshed token from WeChat hub: #{inspect(token)} for appid: #{inspect(appid)}"
)
token
end
def refresh_access_token(appid, authorizer_appid, access_token, hub_base_url) do
Logger.info(
"send refresh_token request to WeChat hub for appid: #{inspect(appid)} with authorizer_appid: #{inspect(authorizer_appid)}, access_token: #{inspect(access_token)}"
)
token =
hub_base_url
|> client()
|> Tesla.post(Url.to_refresh_access_token(), %{
appid: appid,
authorizer_appid: authorizer_appid,
access_token: access_token
})
|> response_to_access_token()
Logger.info(
"received access token from WeChat hub: #{inspect(token)} for appid: #{inspect(appid)} with authorizer_appid: #{inspect(authorizer_appid)}"
)
token
end
def fetch_access_token(appid, hub_base_url) do
hub_base_url
|> client()
|> Tesla.get(Url.to_fetch_access_token(), query: [appid: appid])
|> response_to_access_token()
end
def fetch_access_token(appid, authorizer_appid, hub_base_url) do
hub_base_url
|> client()
|> Tesla.get(Url.to_fetch_access_token(),
query: [appid: appid, authorizer_appid: authorizer_appid]
)
|> response_to_access_token()
end
def fetch_component_access_token(appid, hub_base_url) do
hub_base_url
|> client()
|> Tesla.get(Url.to_fetch_component_access_token(), query: [appid: appid])
|> response_to_access_token()
end
def refresh_component_access_token(appid, access_token, hub_base_url) do
hub_base_url
|> client()
|> Tesla.post(
Url.to_refresh_component_access_token(),
%{appid: appid, access_token: access_token}
)
|> response_to_access_token()
end
def fetch_ticket(appid, type, hub_base_url) do
hub_base_url
|> client()
|> Tesla.get(Url.to_fetch_ticket(), query: [appid: appid, type: type])
|> response_to_ticket()
end
def fetch_ticket(appid, authorizer_appid, type, hub_base_url) do
hub_base_url
|> client()
|> Tesla.get(Url.to_fetch_ticket(),
query: [appid: appid, authorizer_appid: authorizer_appid, type: type]
)
|> response_to_ticket()
end
defp client(hub_base_url) do
Tesla.client(
[
{Tesla.Middleware.BaseUrl, hub_base_url},
{Tesla.Middleware.Retry, delay: 500, max_retries: 10},
Tesla.Middleware.JSON
],
WeChat.Application.http_adapter()
)
end
defp response_to_access_token(
{:ok,
%{
status: 200,
body: %{
"access_token" => access_token,
"timestamp" => timestamp,
"expires_in" => expires_in
}
}}
)
when access_token != nil and access_token != "" and
is_integer(timestamp) and is_integer(expires_in) do
{
:ok,
%WeChat.Token{
access_token: access_token,
timestamp: timestamp,
expires_in: expires_in
}
}
end
defp response_to_access_token({:ok, %{status: 200, body: %{"access_token" => _access_token}}}) do
{
:error,
%Error{
reason: "invalid_access_token",
errcode: -1,
message: "check response failed, invalid value of access_token"
}
}
end
defp response_to_access_token(
{:ok,
%{
body: %{
"errcode" => errcode,
"http_status" => http_status,
"message" => message,
"reason" => reason
}
}}
) do
{
:error,
%Error{
errcode: errcode,
http_status: http_status,
message: message,
reason: reason
}
}
end
defp response_to_access_token({:error, error}) do
{
:error,
%Error{
reason: "fail_fetch_access_token",
errcode: -1,
message: error
}
}
end
defp response_to_ticket(
{:ok,
%{
status: 200,
body:
%{"ticket" => ticket, "timestamp" => timestamp, "expires_in" => expires_in} = body
}}
)
when ticket != nil and ticket != "" and
is_integer(timestamp) and is_integer(expires_in) do
{
:ok,
%WeChat.Ticket{
value: ticket,
type: Map.get(body, "type"),
timestamp: timestamp,
expires_in: expires_in
}
}
end
defp response_to_ticket({:ok, %{status: status, body: body}}) do
{:error, %Error{reason: "fail_fetch_ticket", errcode: -1, http_status: status, message: body}}
end
defp response_to_ticket({:error, error}) do
{:error, %Error{reason: "fail_fetch_ticket", errcode: -1, message: error}}
end
end