コンテンツにスキップ

着せ替えアプリとの接続

自社アプリから着せ替えアプリへのリンクを張り、ID連携を実装することで、エンドユーザは着せ替え操作を簡単に行えるようになります。本ページでは、接続用エンドポイントを使った連携方法について説明します。ウェブアプリケーションや、アプリ内ブラウザ(WebView)から接続用エンドポイントを呼び出すことを想定しています。

Info

Unity SDK を利用する場合、Unity SDK での着せ替えアプリとの接続を参照ください。

Info

UI Kit の着せ替え画面を利用する場合、先に着せ替え画面との接続を参照ください。connectUrl パラメータに指定したエンドポイントで、本ページで説明する内容を実装します。

リンク元のUI実装

Info

UI Kit の着せ替え画面を利用する場合、本項目は読み飛ばし、「接続用のURLを構成する」へ進んで下さい。UI Kit 内に着せ替えアプリへのリンクが設置されるため、自社アプリ側でUIを準備する必要はありません。

Avatar Play へのリンク用のUI(ボタン、テキストリンク、アイコンなど)を、自社アプリの設定画面などに配置します。

ボタン、テキストリンク

ボタンやテキストリンクを使う場合は、「Avatar Play を使う」という文言を利用してください。

ボタンリンク

アイコン

以下のアイコン画像を利用できます。

  1. 120x120ピクセルのアイコン
  2. 180x180ピクセルのアイコン

以下のように、アイコンの近くに「Avatar Play」という文言を配置してください。

アイコンリンク

接続用エンドポイントを構成する

前述のリンクが押された後、接続用エンドポイントを構成してエンドユーザをそこへリダイレクトします。UI Kit の着せ替え画面を利用している場合、 connectUrl パラメータで指定したエンドポイントで接続用のURLを構成し、エンドユーザをリダイレクトします。

エンドポイント仕様

URL

https://fit.avatarplay3d.com/connect

パラメータ

パラメータ 必須 説明 値のサンプル
appId アプリID 1234567890
redirectUri 着せ替えアプリから戻ったときに呼び出される自社アプリのリダイレクトURI(エンドポイントURLまたはカスタムURLスキーム)です。事前にデベロッパーコンソールの「アプリ > アプリの設定 > 変更」から、指定するリダイレクトURLを登録しておく必要があります。 https://example.com/callback
scope パーミッション(後述)をカンマ区切りで連結した文字列 AvatarLoad,ItemGrant
codeChallenge 後述 abcdefghi...
avatarId アップグレード前のゲストユーザのアバターID。アップグレードの場合は指定必須。 1234567890

パーミッション

個別のアバターに対し、自社アプリに与えられた権限です。パーミッションをカンマ区切りで連結してスコープの値を作ります。デフォルトではアバターを表示するパーミッションのみが付与されますが、追加の権限を要求できます。

アバターを表示する権限(AvatarLoad)

エンドユーザのアバターを自社アプリ内で利用する権限です。自社アプリは、アプリ内でエンドユーザ自身のアバターをロードすることはもちろんのこと、自社アプリを利用している他のユーザのアバターをロードすることも可能です。自社アプリを利用していないユーザのアバターをロードすることはできません。

アイテムを付与する権限(ItemGrant)

自社アプリがエンドユーザにアバターアイテムを付与する権限です。許可されたアプリのみが要求できます。

codeChallenge の生成

codeChallenge は、以下のパラメータを連結した文字列から作られるハッシュ値です。

パラメータ 説明 サンプル値
appId アプリID。自社アプリに割り振られたIDです。コンソールの「アプリ > アプリの設定」から確認できます。 1234567890
responseType レスポンスタイプ。「grant_token」という文字で固定です。 grant_token
redirectUri 前述の redirectUri と同じ値です。 https://example.com/callback
scope 前述の scope と同じ値です。 AvatarLoad,GrantItem
requestDate リクエスト時のUnixタイムスタンプ(秒)です。ユーザと紐付けてサーバ側に一時的に保持しておきます。後述の AccessToken API で必要になります。 1552893948
apiKey APIキーを指定します。 abcdefghi...
codeVerifier コード検証用の乱数です。48文字以上のランダム文字列を生成し、ユーザと紐付けてサーバ側に一時的に保持しておきます。後述の AccessToken API へのリクエストで必要になります。 qwertyuio...

これらの値を、半角コロン「:」区切りで上述の順に連結し、SHA256でハッシュ化します。

$ APP_ID={アプリID}
$ RESPONSE_TYPE=grant_token
$ REDIRECT_URI=https://example.com/callback
$ SCOPE=AvatarLoad,ItemGrant
$ REQUEST_DATE=`date +%s`
$ API_KEY={APIキー}

# codeVerifier
$ CODE_VERIFIER=`cat /dev/urandom | base64 | fold -w 48 | head -n 1`

# codeChallenge
$ CODE_CHALLENGE=`echo -n "$APP_ID:$RESPONSE_TYPE:$REDIRECT_URI:$SCOPE:$REQUEST_DATE:$API_KEY:$CODE_VERIFIER" | shasum -a 256 | cut -d' ' -f 1`
appID := "{アプリID}"
responseType := "grant_token"
redirectURI := "https://example.com/callback"
scope := "AvatarLoad,ItemGrant"
requestDate := time.Now().Unix()
apiKey := "{APIキー}"

// codeVerifier
codeVerifierBytes := make([]byte, 48)
if _, err := io.ReadFull(rand.Reader, codeVerifierBytes); err != nil {
    log.Fatal("code verifier generate failed")
    return err
}
codeVerifier = hex.EncodeToString(codeVerifierBytes)

// リダイレクトURI、スコープ、リクエスト日時、codeVerifier はユーザーと紐付けてキャッシュなどに保存しておく

// codeChallenge
codeChallenge := fmt.Sprintf(
    "%s:%s:%s:%s:%d:%s:%s",
    appID,
    responseType,
    redirectURI,
    scope,
    requestDate,
    apiKey,
    codeVerifier,
)
codeChallenge = fmt.Sprintf("%x", sha256.Sum256([]byte(codeChallenge)))
String appId = "{アプリID}";
String responseType = "grant_token";
String redirectUri = "https://example.com/callback";
String scope = "AvatarLoad,ItemGrant";
String requestDate = System.currentTimeMillis() / 1000L;
String apiKey = "{APIキー}";

// codeVerifier
StringBuffer sbCodeVerifier = new StringBuffer();
String alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
for (int i = 0; i < 48; i++) {
    sbCodeVerifier.append(alphabet.charAt((int)(alphabet.length()*Math.random())));
}
codeVerifier = sbCodeVerifier.toString();

// リダイレクトURI、スコープ、リクエスト日時、codeVerifier はユーザーと紐付けてキャッシュなどに保存しておく

// codeChallenge
String codeChallenge = String.format(
    "%s:%s:%s:%s:%d:%s:%s",
    appId,
    responseType,
    redirectUri,
    scope,
    requestDate,
    apiKey,
    codeVerifier
);

MessageDigest sha256;
try {
    sha256 = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
    throw new ServletException(e);
}
codeChallenge = String.format("%040x", new BigInteger(1, sha256.digest(codeChallenge.getBytes())));

接続用エンドポイントのサンプル

次のようなURLになります。

https://fit.avatarplay3d.com/connect?appId=1234567890&redirectUri=https%3A%2F%2Fexample.com%2Fcallback&scope=AvatarLoad%2CItemGrant&codeChallenge=1147b6f3955a27a45fb731c24dcc73e68fa2d597d8c8c172fd4ddeb8fc5afa52&avatarId=1234567890

リダイレクトURIをハンドリングし、接続を完了する

ユーザは着せ替えアプリをインストールし、自社アプリでの利用許諾に同意した後、redirectUri へ遷移します。自社アプリ側では redirectUri に付与された「token」パラメータを取得し、redirectUri, scope, requestDate, codeVerifier と一緒に AccessToken API へ引き渡してアバターIDを取得します。

プラットフォーム別のハンドリング実装方法

カスタムURLスキームなどを使った、一般的な手法でハンドリングを実装します。

ウェブ

ウェブアプリケーションの場合、redirectUri に指定したURLをユーザから直接受信して「token」パラメータを取得します。

Android

Android アプリケーションの場合、マニフェストにディープリンクまたはアプリリンクを定義し、そのリンクを redirectUri に指定します。redirectUri をアプリで受信した後は、インテントからデータを読み取り、「token」パラメータを取得します。

iOS

iOS アプリケーションの場合、カスタムURLスキームまたはユニバーサルリンクを設定し、そのリンクを redirectUri に指定します。 redirectUri をアプリで受信した後は、それぞれのハンドリング実装で「token」パラメータを取得します。

Unity

Unity アプリケーションの場合はプラットフォーム別のプラグインを実装することになります。Unity SDK にサンプル実装が同梱しています。

AccessToken API の呼び出し

$ TOKEN={デコードされたtokenパラメータの値}
$ ENC_TOKEN=`ruby -r cgi -e "puts CGI.escape(\"$TOKEN\")"`
$ ENC_CODE_VERIFIER=`ruby -r cgi -e "puts CGI.escape(\"$CODE_VERIFIER\")"`
$ URL="https://api.avatarplay3d.com/avatar/v1/AccessToken?key=$API_KEY&redirectUri=$ENC_REDIRECT_URI&scope=$ENC_SCOPE&requestDate=$REQUEST_DATE&codeVerifier=$ENC_CODE_VERIFIER&token=$ENC_TOKEN"
$ curl $URL
{
 "avatarId": "1234567890",
 "accessToken": "affs3mqcdwqtlrjahxe6hy0cic6g0xfdwqi4hsnkq487i73o",
 "scope": "AvatarLoad,GrantItem",
 "tokenType": "Bearer"
}
func callback(w http.ResponseWriter, r *http.Request) {
    // 渡されたトークン
    token := r.URL.Query().Get("token")

    // リクエストURL
    requestURL := fmt.Sprintf("https://api.avatarplay3d.com/avatar/v1/AccessToken?key=%s", "{APIキー}")

    // リクエストボディ
    requestParams := url.Values{}
    requestParams.Add("token", token)
    requestParams.Add("redirectUri", redirectURI)
    requestParams.Add("scope", scope)
    requestParams.Add("requestDate", strconv.FormatInt(requestDate, 10))
    requestParams.Add("codeVerifier", codeVerifier)
    requestBody := strings.NewReader(requestParams.Encode())

    // リクエスト
    req, err := http.NewRequest("POST", requestURL, requestBody)
    if err != nil {
        http.Error(w, "new request failed", http.StatusInternalServerError)
        return
    }

    // URLエンコードを指定する
    req.Header.Add("content-type", "application/x-www-form-urlencoded")

    client := &http.Client{
        Timeout: 3 * time.Second,
    }
    res, err := client.Do(req)
    if err != nil {
        http.Error(w, "api failed", http.StatusInternalServerError)
        return
    }

    // レスポンスボディ(JSON)をパース
    apiResponse := struct {
        AvatarID json.Number `json:"avatarId"`
    }{}
    if err := json.NewDecoder(res.Body).Decode(&apiResponse); err != nil {
        log.Printf("decode failed: %+v", err)
        http.Error(w, "decode failed", http.StatusInternalServerError)
        return
    }

    // 得られたアバターIDと自社アプリのユーザを紐付ける
    avatarID, _ = apiResponse.AvatarID.Int64()
}
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        // 渡されたトークン
        String token = request.getParameter("token");

        // リクエストURL
        String requestUrl = String.format("https://api.avatarplay3d.com/avatar/v1/AccessToken?key=%s", "{APIキー}");

        // リクエストボディ
        String requestBody = String.format(
                "token=%s&redirectUri=%s&scope=%s&requestDate=%d&codeVerifier=%s",
                URLEncoder.encode(token, "UTF-8"),
                URLEncoder.encode(redirectUri, "UTF-8"),
                URLEncoder.encode(scope, "UTF-8"),
                requestDate,
                codeVerifier);

        String res;
        HttpURLConnection conn = null;
        try {
            conn = (HttpURLConnection) new URL(requestUrl).openConnection();
            conn.setRequestMethod("POST");
            conn.setDoOutput(true);

            // URLエンコードを指定する
            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

            // ボディ
            OutputStreamWriter writer = null;
            try {
                writer = new OutputStreamWriter(new BufferedOutputStream(conn.getOutputStream()));
                writer.write(requestBody);
            } finally {
                if (writer != null) {
                    writer.close();
                }
            }

            // リクエスト
            StringBuffer sb = new StringBuffer();
            BufferedReader reader = null;
            try {
                reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                String line;
                while ((line = reader.readLine()) != null) {
                  sb.append(line);
                }
            } finally {
                if (reader != null) {
                    reader.close();
                }
            }

            res = sb.toString();
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }

        // 得られたアバターIDと自社アプリのユーザを紐付ける
        JSONObject o = new JSONObject(res);
        avatarId = o.getLong("avatarId");
    }

Info

ゲストユーザからのアップグレードの場合は、さらにゲストユーザのアバターIDを avatarId パラメータに指定します。

ここで得られる「avatarId」の値が正規ユーザのアバターIDとなるため、自社アプリのユーザと紐づけ、以降ユーザのアバターIDとして利用します。RESTful API のみを利用する場合、アクセストークンは必要ありません。サーバ側で一時的に保持していた requestDate, codeVerifier は破棄します。

接続完了後の着せ替えアプリの起動

Info

UI Kit の着せ替え画面を利用する場合、本項目は読み飛ばして下さい。UI Kit の着せ替え画面呼び出し時に、着せ替えアプリが自動起動します。

正規ユーザに対しては、単純に着せ替えアプリを起動するエンドポイントへ誘導して下さい。

エンドポイント

URL

https://fit.avatarplay3d.com/launch

パラメータ

パラメータ名 説明 サンプル値
appId アプリID 1234567890

サンプル

https://fit.avatarplay3d.com/launch?appId=1234567890

ユーザステータスの判別

ユーザのステータスは、Avatar API から取得できます。毎度のAPI接続を回避する場合、アバターIDの紐づけ方で説明した紐付けテーブルに、ユーザステータスの列を加え、その値を参照するようにします。通常は、ゲストユーザかどうかを示す boolean 型のカラムを追加し、ゲストユーザの場合は false、正規ユーザに切り替わったタイミングで true に更新します。

着せ替えアプリから戻った後のアバター再ロード

着せ替えアプリから自社アプリへ戻った際は、アバターが更新されている可能性があるため、再ロードすることが好ましいです。

ウェブアプリケーションの場合、タブのコンテンツが表示状態になったときに発生するイベント visibilitychange を JavaScript でハンドリングして再ロードできます。以下は、画面全体を再ロードする実装例です。

document.addEventListener("visibilitychange", function() {
    if (document.visibilityState == "visible") {
        window.location.reload();
    }
});

ユーザのアップグレードについて

自社アプリ側でゲストユーザを作成し、その後着せ替えアプリと接続して正規ユーザになることを、アップグレードと呼びます。アップグレード時には、ゲストユーザの所持アイテムが正規ユーザに引き継がれます。正規ユーザは、Avatar Play で認証済みのユーザとして扱われ、アバターと所持アイテムをアプリ間で共有して利用できるようになります。

アップグレード済みのゲストユーザの扱い

連携処理が最終工程で失敗するケースを考慮し、アップグレード後もゲストユーザのアバターIDは利用可能にしています。ただし、アイテムを付与することはできません(アイテム付与APIを呼び出してもエラーが返ります)。連携処理の失敗を検知した後は、速やかに再度連携するようユーザーに促してください。

Info

UI Kit 着せ替え画面を利用している場合、UI Kit の画面内で再連携へ誘導されます。

1ユーザの1アプリに対するアップグレード回数制限

アップグレード時にゲストユーザの所持アイテムを引き継げる仕様であるため、アプリのインストール・アンインストールを繰り返すことで、ゲストユーザ時に得たアイテムを一つの正規ユーザに集約できてしまいます。一方で、アップグレードを1ユーザ1度きりに限定してしまうと、何らかしらの理由で前のユーザにログインできなくなり新規登録から始めることになった際に、前のアバターに復帰できなくなるリスクがあります。

この問題に対処するために、1ユーザの1アプリに対するアップグレード回数を制限することを可能にしています。デフォルトでは10が設定されていますが、アプリ毎に変更可能です。


最終更新日: 2020-08-18


最終更新日: 2020-08-18