试用简易的 JavaScript 版 AWS AppSync

首先

你好,今天是2018年Serverless Advent Calendar的第十九天。
过去我只是观看过这个Advent Calendar,但是我决定第一次写一篇关于我喜欢的Serverless的文章。

我和Serverless Advent Calendar 2018

・因为我既省钱又容易厌倦,所以对按量计费的无服务器(Serverless)很感兴趣
・想要开发移动应用程序,但在认证/套接字/推送通知等方面遇到了困难
・DynamoDB的设计不太理想,且API数量不断增加…(到这一点我已经失去了动力)
・参加了DevDayTokyo2018的ServerlessDay活动
・在其中的一个讲座中感受到了AWS AppSync的优秀之处,并确定了DynamoDB的设计指南
・在我的热情消退之前参加了AdventCalendar活动!

我个人的经历

・有着10年经验的系统工程师
・工作中没有使用AWS
・在业余时间稍微有一些APIGateway+Lambda+DynamoDB的经验(使用了SAM)
・初次接触AWS AppSync(也是初次接触Cognito和GraphQL)

马上开始教程。

为了加深理解,以下是一个可行的中文翻译选项:
以以下为目标,“尽可能地”进行手动制作,以增进理解:
· 使用Cognito进行用户认证(包括邮件认证和Google关联)
· 通过GraphQL进行数据访问和更新(基于认证用户的数据访问控制)
· 利用GraphQL的订阅功能进行通知

排列

・创建Cognito UserPool(需要设置AppSync)
・在AppSync中创建API
・尝试从网站通过GraphQL进行数据访问和订阅

创建Cognito UserPool

image.png
image.png
image.png
image.png
image.png

请记下以下内容:
“Pool ID” ⇒ 区域_随机字符串:us-west-2_xxxxxxxxx
“应用程序客户端ID” ⇒ 随机字符串:xxxxxxxxxxxxxxxxxxxxxxxxxx

使用电子邮件认证创建用户

image.png
image.png
undefined
undefined
undefined
image.png
image.png

通过与谷歌的合作创建用户。

image.png
undefined
image.png

请设置以下内容作为已认可的重定向URI:
https://设置的域名.auth.区域.amazoncognito.com/oauth2/idpresponse

undefined
image.png
image.png
image.png
image.png

Appsync 的配置设置

image.png
image.png
image.png

在“设置”中查看并记录设置,以便确认AppSync已创建
API URL
API ID

image.png
undefined

尝试从网站使用Cognito登录和GraphQL获取/注册数据。

要调用GraphQL,需要有jwt(访问令牌)。
jwt是在授权页面上进行授权后,通过重定向URL跳转时使用查询参数[code],可以从cognito的端点获取。

使用以下顺序来解释登录处理的步骤:
– 加载外部JavaScript文件
– 自定义函数集
– 执行登录处理

<html>
<head>
  <meta charset="UTF-8">
  <!-- 外部javascriptの読み込み -->
  <script
  src="https://code.jquery.com/jquery-3.3.1.min.js"
  integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
  crossorigin="anonymous"></script>
  <script type="module" src="src/aws-exports.js"></script>
  <script src="src/aws-sdk.js"></script>
  <script src="src/config.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js" type="text/javascript"></script>

  <!-- 独自関数群 -->
  <script>
    var _resolve = function(x){ return $.Deferred().resolve(x).promise(); };
    var _reject = function(x){ return $.Deferred().reject(x).promise(); };
    var _getStorageItem = function(x){
      var _item = JSON.parse(localStorage.getItem(x));
      return _item ? _item : {};
    };

    _param={};
    location.search.slice(1).split("&").forEach(v=>{
      key = v.split("=")[0];
      _param[key] = v.replace(key+"=","");
    });
    var _cognito = {
      storage : {
        setTokens : _tokens => {
          localStorage.setItem("tokens", JSON.stringify(_tokens));
          _cognito.storage.jwt = _tokens.access_token;
          return _resolve(_tokens);
        },
        jwt : _getStorageItem("tokens").access_token
      },
      api : {
        getTokens : _code => {
          return $.ajax({
            url: opt.aws_cognito_tokenEndpoint,
            method: "POST",
            contentType: "application/x-www-form-urlencoded",
            data: {
              grant_type: "authorization_code",
              client_id: opt.aws_cognito_clientid,
              redirect_uri: opt.aws_cognito_redirecturl,
              code: _code,
            },
          })
        },
        getUser : () => {
          return $.ajax({
            url: opt.aws_cognito_userEndpoint,
            method: "GET",
            contentType: "application/json",
            headers: {
              Authorization: "Bearer "+_cognito.storage.jwt
            },
          })
        },
        graphql : (_query) => {
          return $.ajax({
            url: opt.aws_appsync_graphqlEndpoint,
            method: "POST",
            contentType: "application/json",
            headers: {
              Authorization: _cognito.storage.jwt
            },
            data: JSON.stringify(_query),
          })
        },
      },
    };

    //ログイン処理
    typeof _cognito.storage.jwt === "undefined" && !_param.code ? location.href = opt.aws_cognito_loginEndpoint : ""; //token, codeなし
    (_cognito.storage.jwt
      ? _resolve(_cognito.storage.jwt)
      : _cognito.api.getTokens(_param.code) //token取得
        .then(x=>_cognito.storage.setTokens(x)) //storageに保存
        .then(x=>location.href=opt.aws_cognito_redirecturl)
    )
    .then(() =>
      _cognito.api.graphql({
        query: `
          {
            getMyInfo {
              id
              email
            }
          }
        `})
    )
    .then(x=>{
      return x.data.getMyInfo
      ? _resolve(console.log(x.data.getMyInfo))
      : _cognito.api.getUser() //ユーザ情報取得
        .then(x=>_cognito.api.graphql({ //ユーザ作成
          operationName: "myInfo",
          query: `
            mutation myInfo($input: CreateMyInfoInput!){
              createMyInfo(input: $input){
                email
              }
            }`,
          variables: {
            "input": {
              "email": x.email
            }
          }
        }));
    })
    .fail(x=>_cognito.storage.setTokens({}).then(x=>location.href = opt.aws_cognito_loginEndpoint));
  </script>
</head>
<body>
</body>
</html>

外部JavaScript的加载

・从CDN获取的jQuery
・使用amplify命令创建的aws-exports.js
・aws-sdk.js
・用于定义常量等的config.js(这是我从头开始创建的js文件)
・从Paho Javascript Client下载的mqttws31.min.js

  <!-- 外部javascriptの読み込み -->
  <script
  src="https://code.jquery.com/jquery-3.3.1.min.js"
  integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
  crossorigin="anonymous"></script>
  <script type="module" src="src/aws-exports.js"></script>
  <script src="src/aws-sdk.js"></script>
  <script src="src/config.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js" type="text/javascript"></script>

单独的函数集合

・用于Promise的_resolve、_reject函数
・用于访问本地存储的_getStorageItem函数
・用于存储获取到的Get参数结果的_param对象

var _resolve = function(x){ return $.Deferred().resolve(x).promise(); };
var _reject = function(x){ return $.Deferred().reject(x).promise(); };
var _getStorageItem = function(x){
  var _item = JSON.parse(localStorage.getItem(x));
  return _item ? _item : {};
};

_param={};
location.search.slice(1).split("&").forEach(v=>{
  key = v.split("=")[0];
  _param[key] = v.replace(key+"=","");
});

・用于Cognito/GraphQL访问的_cognito对象

関数名説明_cognito.storage.setTokenscognitoから取得したアクセストークンをローカルストレージに保存する関数_cognito.storage.jwtアクセストークンを格納する変数_cognito.api.getTokenscognitoからアクセストークンを取得する関数_cognito.api.getUsercognitoからログイン中のユーザ情報を取得する関数_cognito.api.graphqlAppsyncのGraphQL APIを呼び出す関数
var _cognito = {
  storage : {
    setTokens : _tokens => {
      localStorage.setItem("tokens", JSON.stringify(_tokens));
      _cognito.storage.jwt = _tokens.access_token;
      return _resolve(_tokens);
    },
    jwt : _getStorageItem("tokens").access_token
  },
  api : {
    getTokens : _code => {
      return $.ajax({
        url: opt.aws_cognito_tokenEndpoint,
        method: "POST",
        contentType: "application/x-www-form-urlencoded",
        data: {
          grant_type: "authorization_code",
          client_id: opt.aws_cognito_clientid,
          redirect_uri: opt.aws_cognito_redirecturl,
          code: _code,
        },
      })
    },
    getUser : () => {
      return $.ajax({
        url: opt.aws_cognito_userEndpoint,
        method: "GET",
        contentType: "application/json",
        headers: {
          Authorization: "Bearer "+_cognito.storage.jwt
        },
      })
    },
    graphql : (_query) => {
      return $.ajax({
        url: opt.aws_appsync_graphqlEndpoint,
        method: "POST",
        contentType: "application/json",
        headers: {
          Authorization: _cognito.storage.jwt
        },
        data: JSON.stringify(_query),
      })
    },
  },
};

登录操作

如果JWT未存储在存储中,并且参数中没有code,则将重定向到认证页面。

//ログイン処理
typeof _cognito.storage.jwt === "undefined" && !_param.code ? location.href = opt.aws_cognito_loginEndpoint : ""; //token, codeなし

1. 如果本地存储中存在 JWT,则进入步骤2;如果不存在 JWT,则从 Cognito 获取并保存到本地存储。
2. 获取到 JWT 后,从 GraphQL(DynamoDB)中获取自己的信息。
3. 如果成功获取到自己的信息,则将其输出到控制台并结束。
4. 如果无法获取到自己的信息,则在 DynamoDB 中创建数据(通过从 Cognito 获取电子邮件信息并在 GraphQL 中执行)。

(_cognito.storage.jwt
  ? _resolve(_cognito.storage.jwt)
  : _cognito.api.getTokens(_param.code) //1.cognitoからtoken取得
    .then(x=>_cognito.storage.setTokens(x)) //1.storageに保存
    .then(x=>location.href=opt.aws_cognito_redirecturl)
)
.then(() =>
  _cognito.api.graphql({ //2.自身の情報を取得する
    query: `
      {
        getMyInfo {
          id
          email
        }
      }
    `})
)
.then(x=>{
  return x.data.getMyInfo
  ? _resolve(console.log(x.data.getMyInfo)) //3. コンソールに出力して終了
  : _cognito.api.getUser() //4. cognitoからユーザ情報取得
    .then(x=>_cognito.api.graphql({ //4. GraphQLでユーザ作成
      operationName: "myInfo",
      query: `
        mutation myInfo($input: CreateMyInfoInput!){
          createMyInfo(input: $input){
            email
          }
        }`,
      variables: {
        "input": {
          "email": x.email //4. cognitoから取得したユーザ情報のemailを利用する
        }
      }
    }));
})
.fail(x=>_cognito.storage.setTokens({}).then(x=>location.href = opt.aws_cognito_loginEndpoint)); //エラー時はtokenを全て消してリダイレクトする(最初からやり直し)

取得“getMyInfo”的解析器如下:
使用Cognito的sub作为ID。

#request mapping template
{
    "version": "2017-02-28",
    "operation": "GetItem",
    "key": {
        "id": { "S" : "${context.identity.sub}" }
    }
}
#response mapping template
$util.toJson($ctx.result)

以下是`createMyInfo`的解析器:
使用cognito的sub作为ID。
在attributeValues中设置input的email。

#request mapping template
{
  "version": "2017-02-28",
  "operation": "PutItem",
  "key": {
    "id": $util.dynamodb.toDynamoDBJson($ctx.identity.sub),
  },
  "attributeValues": $util.dynamodb.toMapValuesJson($ctx.args.input),
  "condition": {
    "expression": "attribute_not_exists(#id)",
    "expressionNames": {
      "#id": "id",
    },
  },
}
#response mapping template
$util.toJson($ctx.result)

我终于实现了登录功能!

关于API的调用,稍微详细解释一下。

获取令牌。

获取从Cognito获取访问令牌的函数。
– URL应设置为“https://设置的域名.auth.区域.amazoncognito.com/oauth2/token”。
– client_id应设置为在用户池中设置的“应用程序客户端ID”。
– redirect_uri应与应用程序客户端设置的“回调URL”匹配。
– code应设置为认证页面重定向时提供的Get参数code。

getTokens : _code => {
  return $.ajax({
    url: opt.aws_cognito_tokenEndpoint,
    method: "POST",
    contentType: "application/x-www-form-urlencoded",
    data: {
      grant_type: "authorization_code",
      client_id: opt.aws_cognito_clientid,
      redirect_uri: opt.aws_cognito_redirecturl,
      code: _code,
    },
  })
}

获取用户信息

使用Cognito获取当前登录用户信息的函数。
·将URL设置为”https://设置的域名.auth.地区.amazoncognito.com/oauth2/userInfo”。
·将授权头部设置为”Bearer ” + jwt。

getUser : () => {
  return $.ajax({
    url: opt.aws_cognito_userEndpoint,
    method: "GET",
    contentType: "application/json",
    headers: {
      Authorization: "Bearer "+_cognito.storage.jwt
    },
  })
}

认知.api.graphql

调用Appsync的GraphQL API函数
– 将url设置为Appsync“设置”中的API URL
– 在Authorization头中设置jwt作为授权

graphql : (_query) => {
  return $.ajax({
    url: opt.aws_appsync_graphqlEndpoint,
    method: "POST",
    contentType: "application/json",
    headers: {
      Authorization: _cognito.storage.jwt
    },
    data: JSON.stringify(_query),
  })
}

通过网站测试Appsync的订阅功能。

以下是按照以下顺序检查订阅的步骤:
1. 执行GraphQL的subscription。
2. 接收返回值中的MQTT连接信息,创建并连接Paho.MQTT.Client。
3. 连接后,进行订阅并接收消息。
4. 收到消息后,在控制台上输出日志。

var onConnect = function(x){ //3.接続後、subscribeをしてメッセージを受け付ける関数
  console.log("onConnect!");
  console.log(x);
  client.subscribe(sub.topics);
};
var onMessageArrived = function(x){ //4.メッセージを受け取ると、コンソールにログを出力する関数
  console.log("onMessageArrived!");
  console.log(x.payloadString);
};
var onConnectionLost = function(x){
  console.log("onConnectionLost!");
  console.log(x);
};
//subscription開始
_cognito.api.graphql({ //1.GraphQLのsubscriptionを実行する
  query: `
    subscription NewPostSub {
      onCreatePost {
        id
        postDate
        message
      }
    }
  `
})
.then(x=>{ //2.戻り値でMQTT接続情報が返って来るため、Paho.MQTT.Clientを作成し、接続する
  sub = {
    wssUrl: x.extensions.subscription.mqttConnections[0].url,
    client: x.extensions.subscription.mqttConnections[0].client,
    topics: x.extensions.subscription.mqttConnections[0].topics[0],
  }
  client = new Paho.MQTT.Client(sub.wssUrl, sub.client);
  var connectOptions = {
      onSuccess: onConnect, //3.接続後、subscribeをしてメッセージを受け付ける関数の登録
      useSSL: true,
      timeout: 3,
      mqttVersion: 4,
      onFailure: function(x) {
          console.log("connect failed!");
          console.log(x);
      }
  };
  client.onMessageArrived = onMessageArrived; //4.メッセージを受け取ると、コンソールにログを出力する関数の登録
  client.onConnectionLost = onConnectionLost;
  client.connect(connectOptions);
})
.fail(x=>console.log(x));

image.png

結束

感谢您的观看。
尽管日语信息相当有限且困难,但我已经成功使用简单的JavaScript实现了对Cognito的登录,并使用Appsync执行了GraphQL查询/变异/订阅。
接下来,我想确认如何创建房间并使特定用户仅能进行订阅,以及在离线状态下的操作等内容。

如果有不足之处,请您留下评论,我将不胜感激。

广告
将在 10 秒后关闭
bannerAds