试用简易的 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





请记下以下内容:
“Pool ID” ⇒ 区域_随机字符串:us-west-2_xxxxxxxxx
“应用程序客户端ID” ⇒ 随机字符串:xxxxxxxxxxxxxxxxxxxxxxxxxx
使用电子邮件认证创建用户







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



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





Appsync 的配置设置



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


尝试从网站使用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对象
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));

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