├── frontend ├── .env.local.example ├── .browserslistrc ├── src │ ├── assets │ │ ├── css │ │ │ └── tailwind.css │ │ └── logo.png │ ├── main.ts │ ├── shims-vue.d.ts │ ├── App.vue │ └── components │ │ └── LiffDev.vue ├── public │ ├── favicon.ico │ └── index.html ├── postcss.config.js ├── tailwind.config.js ├── .gitignore ├── README.md ├── .eslintrc.js ├── tsconfig.json └── package.json ├── api ├── .funcignore ├── proxies.json ├── tsconfig.json ├── host.json ├── GetProfile │ ├── function.json │ └── index.ts ├── local.settings.example.json ├── package.json ├── core │ └── GameManager.ts ├── Bet │ ├── function.json │ └── index.ts └── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── docs ├── images │ ├── github_secrets_001.png │ ├── github_secrets_002.png │ ├── github_secrets_003.png │ ├── github_secrets_004.png │ ├── github_workflow_001.png │ ├── github_workflow_002.png │ ├── github_workflow_003.png │ ├── liff-app_on-web_001.png │ ├── liff-app_on-web_002.png │ ├── structure-overview.png │ ├── liff-app_on-line_001.png │ ├── liff-app_on-line_002.png │ ├── github_edit-workflow_001.png │ ├── github_edit-workflow_002.png │ ├── github_edit-workflow_003.png │ ├── github_edit-workflow_004.png │ ├── github_edit-workflow_005.png │ ├── github_edit-workflow_006.png │ ├── github_edit-workflow_007.png │ ├── github_edit-workflow_008.png │ ├── github_edit-workflow_009.png │ ├── azure-portal_deployment_001.png │ ├── azure-portal_deployment_002.png │ ├── azure-portal_deployment_003.png │ ├── azure-portal_deployment_004.png │ ├── azure-portal_resource-group.png │ ├── azure-portal_get-cosmosdb-connstring_001.png │ ├── azure-portal_get-cosmosdb-connstring_002.png │ ├── azure-portal_get-cosmosdb-connstring_003.png │ ├── azure-portal_get-cosmosdb-connstring_004.png │ ├── line-develpers-console_create-channel_001.png │ ├── line-develpers-console_create-channel_002.png │ ├── line-devepolers-console_liff-setting_001.png │ ├── line-devepolers-console_liff-setting_002.png │ ├── line-devepolers-console_liff-setting_003.png │ ├── line-devepolers-console_liff-setting_004.png │ ├── line-devepolers-console_liff-setting_005.png │ ├── line-develpers-console_create-provider_001.png │ ├── line-develpers-console_create-provider_002.png │ ├── github-repository_create-repository-from-template.png │ ├── azure-portal_static-web-apps_set-func-app-settings_001.png │ ├── azure-portal_static-web-apps_set-func-app-settings_002.png │ ├── azure-portal_static-web-apps_set-func-app-settings_003.png │ ├── azure-portal_static-web-apps_set-func-app-settings_004.png │ ├── github-repository_create-repository-from-template_001.png │ ├── github-repository_create-repository-from-template_002.png │ ├── github_develper-settings_generate-personal-access-token_001.png │ └── github_develper-settings_generate-personal-access-token_002.png ├── run-locally.md └── self-paced-handson.md ├── LICENSE ├── arm-templates ├── README.md └── template.json ├── README.md └── .github └── workflows └── deploy-azure-static-web-apps.yml /frontend/.env.local.example: -------------------------------------------------------------------------------- 1 | VUE_APP_LIFF_ID= -------------------------------------------------------------------------------- /frontend/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /api/.funcignore: -------------------------------------------------------------------------------- 1 | *.js.map 2 | *.ts 3 | .git* 4 | .vscode 5 | local.settings.json 6 | test 7 | tsconfig.json -------------------------------------------------------------------------------- /api/proxies.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/proxies", 3 | "proxies": {} 4 | } 5 | -------------------------------------------------------------------------------- /frontend/src/assets/css/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-azuretools.vscode-azurefunctions" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/frontend/src/assets/logo.png -------------------------------------------------------------------------------- /docs/images/github_secrets_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/github_secrets_001.png -------------------------------------------------------------------------------- /docs/images/github_secrets_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/github_secrets_002.png -------------------------------------------------------------------------------- /docs/images/github_secrets_003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/github_secrets_003.png -------------------------------------------------------------------------------- /docs/images/github_secrets_004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/github_secrets_004.png -------------------------------------------------------------------------------- /docs/images/github_workflow_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/github_workflow_001.png -------------------------------------------------------------------------------- /docs/images/github_workflow_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/github_workflow_002.png -------------------------------------------------------------------------------- /docs/images/github_workflow_003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/github_workflow_003.png -------------------------------------------------------------------------------- /docs/images/liff-app_on-web_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/liff-app_on-web_001.png -------------------------------------------------------------------------------- /docs/images/liff-app_on-web_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/liff-app_on-web_002.png -------------------------------------------------------------------------------- /docs/images/structure-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/structure-overview.png -------------------------------------------------------------------------------- /docs/images/liff-app_on-line_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/liff-app_on-line_001.png -------------------------------------------------------------------------------- /docs/images/liff-app_on-line_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/liff-app_on-line_002.png -------------------------------------------------------------------------------- /docs/images/github_edit-workflow_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/github_edit-workflow_001.png -------------------------------------------------------------------------------- /docs/images/github_edit-workflow_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/github_edit-workflow_002.png -------------------------------------------------------------------------------- /docs/images/github_edit-workflow_003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/github_edit-workflow_003.png -------------------------------------------------------------------------------- /docs/images/github_edit-workflow_004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/github_edit-workflow_004.png -------------------------------------------------------------------------------- /docs/images/github_edit-workflow_005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/github_edit-workflow_005.png -------------------------------------------------------------------------------- /docs/images/github_edit-workflow_006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/github_edit-workflow_006.png -------------------------------------------------------------------------------- /docs/images/github_edit-workflow_007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/github_edit-workflow_007.png -------------------------------------------------------------------------------- /docs/images/github_edit-workflow_008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/github_edit-workflow_008.png -------------------------------------------------------------------------------- /docs/images/github_edit-workflow_009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/github_edit-workflow_009.png -------------------------------------------------------------------------------- /docs/images/azure-portal_deployment_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/azure-portal_deployment_001.png -------------------------------------------------------------------------------- /docs/images/azure-portal_deployment_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/azure-portal_deployment_002.png -------------------------------------------------------------------------------- /docs/images/azure-portal_deployment_003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/azure-portal_deployment_003.png -------------------------------------------------------------------------------- /docs/images/azure-portal_deployment_004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/azure-portal_deployment_004.png -------------------------------------------------------------------------------- /docs/images/azure-portal_resource-group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/azure-portal_resource-group.png -------------------------------------------------------------------------------- /frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import App from "./App.vue"; 3 | 4 | import '@/assets/css/tailwind.css'; 5 | 6 | createApp(App).mount("#app"); 7 | -------------------------------------------------------------------------------- /docs/images/azure-portal_get-cosmosdb-connstring_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/azure-portal_get-cosmosdb-connstring_001.png -------------------------------------------------------------------------------- /docs/images/azure-portal_get-cosmosdb-connstring_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/azure-portal_get-cosmosdb-connstring_002.png -------------------------------------------------------------------------------- /docs/images/azure-portal_get-cosmosdb-connstring_003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/azure-portal_get-cosmosdb-connstring_003.png -------------------------------------------------------------------------------- /docs/images/azure-portal_get-cosmosdb-connstring_004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/azure-portal_get-cosmosdb-connstring_004.png -------------------------------------------------------------------------------- /docs/images/line-develpers-console_create-channel_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/line-develpers-console_create-channel_001.png -------------------------------------------------------------------------------- /docs/images/line-develpers-console_create-channel_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/line-develpers-console_create-channel_002.png -------------------------------------------------------------------------------- /docs/images/line-devepolers-console_liff-setting_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/line-devepolers-console_liff-setting_001.png -------------------------------------------------------------------------------- /docs/images/line-devepolers-console_liff-setting_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/line-devepolers-console_liff-setting_002.png -------------------------------------------------------------------------------- /docs/images/line-devepolers-console_liff-setting_003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/line-devepolers-console_liff-setting_003.png -------------------------------------------------------------------------------- /docs/images/line-devepolers-console_liff-setting_004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/line-devepolers-console_liff-setting_004.png -------------------------------------------------------------------------------- /docs/images/line-devepolers-console_liff-setting_005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/line-devepolers-console_liff-setting_005.png -------------------------------------------------------------------------------- /docs/images/line-develpers-console_create-provider_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/line-develpers-console_create-provider_001.png -------------------------------------------------------------------------------- /docs/images/line-develpers-console_create-provider_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/line-develpers-console_create-provider_002.png -------------------------------------------------------------------------------- /frontend/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { DefineComponent } from 'vue' 3 | const component: DefineComponent<{}, {}, any> 4 | export default component 5 | } 6 | -------------------------------------------------------------------------------- /docs/images/github-repository_create-repository-from-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/github-repository_create-repository-from-template.png -------------------------------------------------------------------------------- /docs/images/azure-portal_static-web-apps_set-func-app-settings_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/azure-portal_static-web-apps_set-func-app-settings_001.png -------------------------------------------------------------------------------- /docs/images/azure-portal_static-web-apps_set-func-app-settings_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/azure-portal_static-web-apps_set-func-app-settings_002.png -------------------------------------------------------------------------------- /docs/images/azure-portal_static-web-apps_set-func-app-settings_003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/azure-portal_static-web-apps_set-func-app-settings_003.png -------------------------------------------------------------------------------- /docs/images/azure-portal_static-web-apps_set-func-app-settings_004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/azure-portal_static-web-apps_set-func-app-settings_004.png -------------------------------------------------------------------------------- /docs/images/github-repository_create-repository-from-template_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/github-repository_create-repository-from-template_001.png -------------------------------------------------------------------------------- /docs/images/github-repository_create-repository-from-template_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/github-repository_create-repository-from-template_002.png -------------------------------------------------------------------------------- /docs/images/github_develper-settings_generate-personal-access-token_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/github_develper-settings_generate-personal-access-token_001.png -------------------------------------------------------------------------------- /docs/images/github_develper-settings_generate-personal-access-token_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzeyelid/line-liff-with-azure-handson/HEAD/docs/images/github_develper-settings_generate-personal-access-token_002.png -------------------------------------------------------------------------------- /api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "dist", 6 | "rootDir": ".", 7 | "sourceMap": true, 8 | "strict": false 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Attach to Node Functions", 6 | "type": "node", 7 | "request": "attach", 8 | "port": 9229, 9 | "preLaunchTask": "func: host start" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | future: { 3 | // removeDeprecatedGapUtilities: true, 4 | // purgeLayersByDefault: true, 5 | }, 6 | purge: [ 7 | './public/**/*.html', 8 | './src/**/*.vue' 9 | ], 10 | theme: { 11 | extend: {}, 12 | }, 13 | variants: {}, 14 | plugins: [], 15 | } 16 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /api/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "logging": { 4 | "applicationInsights": { 5 | "samplingSettings": { 6 | "isEnabled": true, 7 | "excludedTypes": "Request" 8 | } 9 | } 10 | }, 11 | "extensionBundle": { 12 | "id": "Microsoft.Azure.Functions.ExtensionBundle", 13 | "version": "[1.*, 2.0.0)" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /api/GetProfile/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "authLevel": "anonymous", 5 | "type": "httpTrigger", 6 | "direction": "in", 7 | "name": "req", 8 | "methods": [ 9 | "post" 10 | ] 11 | }, 12 | { 13 | "type": "http", 14 | "direction": "out", 15 | "name": "res" 16 | } 17 | ], 18 | "scriptFile": "../dist/GetProfile/index.js" 19 | } 20 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # playground-line-liff 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "staticWebApps.appSubpath": "/", 3 | "staticWebApps.apiSubpath": "", 4 | "staticWebApps.appArtifactSubpath": "dist", 5 | "azureFunctions.deploySubpath": "api", 6 | "azureFunctions.postDeployTask": "npm install", 7 | "azureFunctions.projectLanguage": "TypeScript", 8 | "azureFunctions.projectRuntime": "~3", 9 | "debug.internalConsoleOptions": "neverOpen", 10 | "azureFunctions.preDeployTask": "npm prune", 11 | "editor.tabSize": 2 12 | } -------------------------------------------------------------------------------- /api/local.settings.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "IsEncrypted": false, 3 | "Values": { 4 | "AzureWebJobsStorage": "", 5 | "FUNCTIONS_WORKER_RUNTIME": "node", 6 | "COSMOS_DB_CONNECTION_STRING": "AccountEndpoint=https://.documents.azure.com:443/;AccountKey=;", 7 | "COSMOS_DB_DATABASE_ID": "games", 8 | "COSMOS_DB_CONTAINER_ID_GLOBAL_RESULTS": "global-results", 9 | "COSMOS_DB_CONTAINER_ID_PLAYER_RESULTS": "player-results" 10 | }, 11 | "Host": { 12 | "CORS": "*" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "build": "tsc", 7 | "watch": "tsc -w", 8 | "prestart": "npm run build", 9 | "start": "func start", 10 | "test": "echo \"No tests yet...\"" 11 | }, 12 | "dependencies": { 13 | "@azure/cosmos": "^3.9.2", 14 | "axios": "^0.21.0" 15 | }, 16 | "devDependencies": { 17 | "@azure/functions": "^1.0.2-beta2", 18 | "@types/node": "^14.14.6", 19 | "typescript": "^3.3.3" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /frontend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: [ 7 | "plugin:vue/vue3-essential", 8 | "eslint:recommended", 9 | "@vue/typescript/recommended", 10 | "@vue/prettier", 11 | "@vue/prettier/@typescript-eslint" 12 | ], 13 | parserOptions: { 14 | ecmaVersion: 2020 15 | }, 16 | rules: { 17 | "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", 18 | "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off" 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 17 | 18 | 28 | -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/run-locally.md: -------------------------------------------------------------------------------- 1 | ### How to prepare 2 | 3 | https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=linux%2Cts%2Cbash#v2 4 | 5 | ```bash 6 | npm install -g @vue/cli 7 | ``` 8 | 9 | ### Run front-end locally 10 | 11 | 初回 12 | 13 | ```bash 14 | cd frontend 15 | cp .env.local.example .env.local 16 | ``` 17 | 18 | `.env.local` を設定します。 19 | 20 | ```bash 21 | npm install 22 | npm run serve 23 | ``` 24 | 25 | 初回以降 26 | 27 | ```bash 28 | cd frontend 29 | npm run serve 30 | ``` 31 | 32 | ### Run functions locally 33 | 34 | ```bash 35 | npm install -g typescript 36 | 37 | cd api 38 | npm install 39 | npm run start 40 | ``` 41 | 42 | 初回以降 43 | 44 | ```bash 45 | cd api 46 | npm run start 47 | ``` 48 | -------------------------------------------------------------------------------- /api/core/GameManager.ts: -------------------------------------------------------------------------------- 1 | export type Stage = { 2 | id: string 3 | } 4 | 5 | export type GlobalResult = { 6 | id?: string 7 | stage: Stage 8 | selectedColor: Color 9 | previousColor: Color 10 | matched: boolean 11 | } 12 | 13 | export type PlayerResult = { 14 | lineUser: LineUser 15 | id?: string 16 | selectedColor: Color 17 | previousColor: Color 18 | matched: boolean 19 | winStreakCount: number 20 | } 21 | 22 | export type LineUser = { 23 | id: string 24 | } 25 | 26 | const ColorTypes = ["red", "green", "blue", "yellow"]; 27 | export type Color = "red" | "green" | "blue" | "yellow"; 28 | 29 | export class GameManager { 30 | public static validColorType(color: string): boolean { 31 | return ColorTypes.find(e => e == color) != undefined; 32 | } 33 | } -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "webpack-env" 16 | ], 17 | "paths": { 18 | "@/*": [ 19 | "src/*" 20 | ] 21 | }, 22 | "lib": [ 23 | "esnext", 24 | "dom", 25 | "dom.iterable", 26 | "scripthost" 27 | ] 28 | }, 29 | "include": [ 30 | "src/**/*.ts", 31 | "src/**/*.tsx", 32 | "src/**/*.vue", 33 | "tests/**/*.ts", 34 | "tests/**/*.tsx" 35 | ], 36 | "exclude": [ 37 | "node_modules" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "func", 6 | "command": "host start", 7 | "problemMatcher": "$func-node-watch", 8 | "isBackground": true, 9 | "dependsOn": "npm build", 10 | "options": { 11 | "cwd": "${workspaceFolder}/api" 12 | } 13 | }, 14 | { 15 | "type": "shell", 16 | "label": "npm build", 17 | "command": "npm run build", 18 | "dependsOn": "npm install", 19 | "problemMatcher": "$tsc", 20 | "options": { 21 | "cwd": "${workspaceFolder}/api" 22 | } 23 | }, 24 | { 25 | "type": "shell", 26 | "label": "npm install", 27 | "command": "npm install", 28 | "options": { 29 | "cwd": "${workspaceFolder}/api" 30 | } 31 | }, 32 | { 33 | "type": "shell", 34 | "label": "npm prune", 35 | "command": "npm prune --production", 36 | "dependsOn": "npm build", 37 | "problemMatcher": [], 38 | "options": { 39 | "cwd": "${workspaceFolder}/api" 40 | } 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /api/Bet/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "authLevel": "anonymous", 5 | "type": "httpTrigger", 6 | "direction": "in", 7 | "name": "req", 8 | "methods": [ 9 | "post" 10 | ], 11 | "route": "Bet/{stageId}" 12 | }, 13 | { 14 | "name": "lastGlobalResult", 15 | "type": "cosmosDB", 16 | "direction": "in", 17 | "connectionStringSetting": "COSMOS_DB_CONNECTION_STRING", 18 | "databaseName": "games", 19 | "collectionName": "global-results", 20 | "partitionKey": "{stageId}", 21 | "sqlQuery": "SELECT * FROM c ORDER BY c._ts DESC OFFSET 0 LIMIT 1" 22 | }, 23 | { 24 | "name": "globalResultsOut", 25 | "type": "cosmosDB", 26 | "direction": "out", 27 | "connectionStringSetting": "COSMOS_DB_CONNECTION_STRING", 28 | "databaseName": "games", 29 | "collectionName": "global-results", 30 | "partitionKey": "{stageId}" 31 | }, 32 | { 33 | "type": "http", 34 | "direction": "out", 35 | "name": "res" 36 | } 37 | ], 38 | "scriptFile": "../dist/Bet/index.js" 39 | } 40 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground-line-liff", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@line/liff": "^2.5.0", 12 | "vue": "^3.0.0" 13 | }, 14 | "devDependencies": { 15 | "@types/node": "^14.14.6", 16 | "@typescript-eslint/eslint-plugin": "^2.33.0", 17 | "@typescript-eslint/parser": "^2.33.0", 18 | "@vue/cli-plugin-babel": "~4.5.0", 19 | "@vue/cli-plugin-eslint": "~4.5.0", 20 | "@vue/cli-plugin-typescript": "~4.5.0", 21 | "@vue/cli-service": "~4.5.0", 22 | "@vue/compiler-sfc": "^3.0.0", 23 | "@vue/eslint-config-prettier": "^6.0.0", 24 | "@vue/eslint-config-typescript": "^5.0.2", 25 | "eslint": "^6.7.2", 26 | "eslint-plugin-prettier": "^3.1.3", 27 | "eslint-plugin-vue": "^7.0.0-0", 28 | "prettier": "^1.19.1", 29 | "tailwindcss": "^1.9.6", 30 | "typescript": "~3.9.3" 31 | }, 32 | "_id": "playground-line-liff@0.1.0", 33 | "readme": "ERROR: No README data found!" 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Kazumi OHIRA (@dz_) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /arm-templates/README.md: -------------------------------------------------------------------------------- 1 | ```bash 2 | cd arm-templates 3 | 4 | RESOURCE_GROUP= 5 | LOCATION=japaneast 6 | 7 | IDENTIFIER= 8 | GITHUB_REPOSITORY_URL=https://github.com// 9 | GITHUB_ACCESS_TOKEN= 10 | 11 | az login 12 | az group create --resource-group ${RESOURCE_GROUP} --location ${LOCATION} 13 | 14 | az deployment group validate \ 15 | --resource-group ${RESOURCE_GROUP} \ 16 | --template-file template.json \ 17 | --parameters \ 18 | identifier="${IDENTIFIER}" \ 19 | staticWebAppRepositoryUrl="${GITHUB_REPOSITORY_URL}" \ 20 | staticWebAppRepositoryToken="${GITHUB_ACCESS_TOKEN}" 21 | 22 | az deployment group what-if \ 23 | --resource-group ${RESOURCE_GROUP} \ 24 | --template-file template.json \ 25 | --parameters \ 26 | identifier="${IDENTIFIER}" \ 27 | staticWebAppRepositoryUrl="${GITHUB_REPOSITORY_URL}" \ 28 | staticWebAppRepositoryToken="${GITHUB_ACCESS_TOKEN}" 29 | 30 | az deployment group create \ 31 | --resource-group ${RESOURCE_GROUP} \ 32 | --template-file template.json \ 33 | --parameters \ 34 | identifier="${IDENTIFIER}" \ 35 | staticWebAppRepositoryUrl="${GITHUB_REPOSITORY_URL}" \ 36 | staticWebAppRepositoryToken="${GITHUB_ACCESS_TOKEN}" 37 | ``` -------------------------------------------------------------------------------- /api/GetProfile/index.ts: -------------------------------------------------------------------------------- 1 | import { AzureFunction, Context, HttpRequest } from "@azure/functions" 2 | import axios from "axios"; 3 | import * as querystring from "querystring"; 4 | 5 | type BodyParams = { 6 | token: string 7 | } 8 | 9 | type LineAccessTokenVerifyParams = { 10 | access_token: string 11 | } 12 | 13 | const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise { 14 | try { 15 | const bodyParams = req.body as BodyParams; 16 | const params: LineAccessTokenVerifyParams = { 17 | access_token: bodyParams.token 18 | }; 19 | const verifyResult = await axios.get( 20 | `https://api.line.me/oauth2/v2.1/verify?${querystring.stringify(params)}` 21 | ); 22 | const profile = await axios.get( 23 | "https://api.line.me/v2/profile", 24 | { 25 | headers: { 26 | "Authorization": `Bearer ${bodyParams.token}` 27 | } 28 | } 29 | ) 30 | context.res = { 31 | status: 200, 32 | body: profile.data 33 | } 34 | } catch (e) { 35 | context.res = { 36 | status: 500, 37 | body: JSON.stringify(e) 38 | } 39 | return; 40 | } 41 | }; 42 | 43 | export default httpTrigger; -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (https://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # TypeScript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | .env.test 64 | 65 | # parcel-bundler cache (https://parceljs.org/) 66 | .cache 67 | 68 | # next.js build output 69 | .next 70 | 71 | # nuxt.js build output 72 | .nuxt 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless/ 79 | 80 | # FuseBox cache 81 | .fusebox/ 82 | 83 | # DynamoDB Local files 84 | .dynamodb/ 85 | 86 | # TypeScript output 87 | dist 88 | out 89 | 90 | # Azure Functions artifacts 91 | bin 92 | obj 93 | appsettings.json 94 | local.settings.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Azure Static Web Apps と Cosmos DB で作る LIFF アプリ 2 | 3 | このハンズオンコンテンツでは、LIFF (LINE Front-end Framework) を Azure Static Web Apps と Cosmos DB を用いてホストするサンプルをご紹介します。 4 | 5 | ここでは、Azure を中心に解説します。LIFF の詳細に関しては、公式ドキュメントをご参照ください。 6 | 7 | - [LINE Front-end Framework | LINE Developers](https://developers.line.biz/ja/docs/liff/overview/) 8 | 9 | ## 構成 10 | 11 | ![](./docs/images/structure-overview.png) 12 | 13 | Azure Static Web Apps でLIFFで構成した静的サイトをホストし、データは Azure Cosmos DB に蓄積します。 フロントエンドと Azure Cosmos DB とのやりとりは、Azure Static Web Apps に統合された FaaS 機能を利用し、API を実装しています。 14 | 15 | | 利用する Azure サービス | 概要 | 低価格に抑えるための料金の目安 | 16 | |----|----|----| 17 | | Azure Static Web Apps | 静的サイトをホストできる PaaS(プレビュー) | プレビューの間無料(GA後は未発表) | 18 | | Azure Functions | イベント駆動のサーバレス コンピューティング、いわゆる FaaS (※ このハンズオンでは Azure Static Web Apps に統合されている) | 従量課金の場合、最初の 100 万回は無料/月。詳細は下記参照。 | 19 | | Azure Cosmos DB | NoSQL データベース | Free tier あり(最初の 400 RU/秒と 5 GB のストレージが無料)詳細は下記参照。 | 20 | 21 | ### Azure Static Web Apps 22 | 23 | - [Azure Static Web Apps のドキュメント | Microsoft Docs](https://docs.microsoft.com/ja-jp/azure/static-web-apps/) 24 | - [価格 - Static Web Apps | Microsoft Azure](https://azure.microsoft.com/ja-jp/pricing/details/app-service/static/) 25 | 26 | ### Azure Functions 27 | 28 | - [Azure Functions のドキュメント | Microsoft Docs](https://docs.microsoft.com/ja-jp/azure/azure-functions/) 29 | - [価格 - Functions | Microsoft Azure](https://azure.microsoft.com/ja-jp/pricing/details/functions/) 30 | 31 | ### Azure Cosmos DB 32 | 33 | - [Azure Cosmos DB | Microsoft Docs](https://docs.microsoft.com/ja-jp/azure/cosmos-db/) 34 | - [Azure Cosmos DB の Free レベル - Azure Cosmos DB での開発とテストのための最適化 | Microsoft Docs](https://docs.microsoft.com/ja-jp/azure/cosmos-db/optimize-dev-test#azure-cosmos-db-free-tier) 35 | - [価格 - Azure Cosmos DB | Microsoft Azure](https://azure.microsoft.com/ja-jp/pricing/details/cosmos-db/) 36 | 37 | ## セルフペースドハンズオン 38 | 39 | ご自身のペースでトライするには、[セルフペースドハンズオン](./docs/self-paced-handson.md)の資料をご利用ください。 40 | 41 | -------------------------------------------------------------------------------- /.github/workflows/deploy-azure-static-web-apps.yml: -------------------------------------------------------------------------------- 1 | name: Azure Static Web Apps CI/CD 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | types: [opened, synchronize, reopened, closed] 9 | branches: 10 | - main 11 | 12 | jobs: 13 | build_and_deploy_job: 14 | if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') 15 | runs-on: ubuntu-latest 16 | name: Build and Deploy Job 17 | steps: 18 | - uses: actions/checkout@v2 19 | with: 20 | submodules: true 21 | - name: Build And Deploy 22 | id: builddeploy 23 | uses: Azure/static-web-apps-deploy@v0.0.1-preview 24 | with: 25 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_KIND_CLIFF_02ED82C00 }} 26 | repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) 27 | action: "upload" 28 | ###### Repository/Build Configurations - These values can be configured to match you app requirements. ###### 29 | # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig 30 | app_location: "frontend" # App source code path 31 | api_location: "api" # Api source code path - optional 32 | app_artifact_location: "dist" # Built app content directory - optional 33 | ###### End of Repository/Build Configurations ###### 34 | env: 35 | VUE_APP_LIFF_ID: ${{ secrets.LIFF_ID }} 36 | 37 | close_pull_request_job: 38 | if: github.event_name == 'pull_request' && github.event.action == 'closed' 39 | runs-on: ubuntu-latest 40 | name: Close Pull Request Job 41 | steps: 42 | - name: Close Pull Request 43 | id: closepullrequest 44 | uses: Azure/static-web-apps-deploy@v0.0.1-preview 45 | with: 46 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_KIND_CLIFF_02ED82C00 }} 47 | action: "close" 48 | -------------------------------------------------------------------------------- /frontend/src/components/LiffDev.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 98 | -------------------------------------------------------------------------------- /api/Bet/index.ts: -------------------------------------------------------------------------------- 1 | import { AzureFunction, Context, HttpRequest } from "@azure/functions"; 2 | import axios from "axios"; 3 | import * as querystring from "querystring"; 4 | import { CosmosClient, SqlQuerySpec } from "@azure/cosmos"; 5 | import { GameManager, GlobalResult, LineUser, PlayerResult, Color } from "../core/GameManager"; 6 | 7 | type HttpRequestBetBody = { 8 | token: string 9 | selectedColor: Color 10 | }; 11 | 12 | type HttpRequestBetParams = { 13 | stageId: string 14 | }; 15 | 16 | type LineAccessTokenVerifyParams = { 17 | access_token: string 18 | }; 19 | 20 | type LineProfile = { 21 | displayName: string 22 | userId: string 23 | pictureUrl: string 24 | statusMessage: string 25 | }; 26 | 27 | interface HttpRequestBet extends HttpRequest { 28 | body: HttpRequestBetBody 29 | params: HttpRequestBetParams 30 | } 31 | 32 | const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequestBet): Promise { 33 | // Validate parameters and request body 34 | if (!req.body) { 35 | context.res = { 36 | status: 400, 37 | body: "Request body is missing" 38 | } 39 | return; 40 | } 41 | 42 | if (!req.body.token) { 43 | context.res = { 44 | status: 400, 45 | body: "Access token is missing" 46 | }; 47 | return; 48 | } 49 | 50 | if (!req.body.selectedColor || !GameManager.validColorType(req.body.selectedColor)) { 51 | context.res = { 52 | status: 400, 53 | body: "Selected color is missing or invalid" 54 | } 55 | return; 56 | } 57 | 58 | const params: LineAccessTokenVerifyParams = { 59 | access_token: req.body.token, 60 | }; 61 | 62 | // Get LINE user ID 63 | let lineUser: LineUser; 64 | try { 65 | const verifyResult = await axios.get( 66 | `https://api.line.me/oauth2/v2.1/verify?${querystring.stringify(params)}` 67 | ); 68 | const profileResponse = await axios.get( 69 | "https://api.line.me/v2/profile", 70 | { 71 | headers: { 72 | "Authorization": `Bearer ${params.access_token}` 73 | } 74 | } 75 | ); 76 | 77 | const lineProfile: LineProfile = profileResponse.data; 78 | lineUser = { 79 | id: lineProfile.userId 80 | }; 81 | } catch (e) { 82 | context.res = { 83 | status: 400, 84 | body: "Access token is invalid or expired" 85 | }; 86 | return; 87 | } 88 | 89 | // Get last player's result 90 | const cosmosDbConnectionString = process.env.COSMOS_DB_CONNECTION_STRING; 91 | const cosmosDbClient = new CosmosClient(cosmosDbConnectionString); 92 | const { database } = await cosmosDbClient.databases.createIfNotExists({ id: process.env.COSMOS_DB_DATABASE_ID }); 93 | const { container } = await database.containers.createIfNotExists({ id: process.env.COSMOS_DB_CONTAINER_ID_PLAYER_RESULTS }); 94 | 95 | const querySpec: SqlQuerySpec = { 96 | query: "SELECT * FROM c WHERE c.lineUser.id = @lineUserId ORDER BY c._ts DESC OFFSET 0 LIMIT 1", 97 | parameters: [ 98 | { 99 | name: "@lineUserId", 100 | value: lineUser.id 101 | } 102 | ] 103 | }; 104 | const { resources: playerResults } = await container.items.query(querySpec).fetchAll(); 105 | const lastPlayerResult: PlayerResult = playerResults[0]; 106 | console.log(lastPlayerResult); 107 | 108 | const lastGlobalResult: GlobalResult = context.bindings.lastGlobalResult[0]; 109 | 110 | // Judge 111 | let matched = false; 112 | let winStreakCount = 0; 113 | if (lastGlobalResult && lastGlobalResult.selectedColor == req.body.selectedColor) { 114 | matched = true; 115 | winStreakCount = ++lastPlayerResult.winStreakCount; 116 | } 117 | 118 | const playerResult: PlayerResult = { 119 | lineUser, 120 | selectedColor: req.body.selectedColor, 121 | previousColor: lastGlobalResult ? lastGlobalResult.selectedColor : null, 122 | matched, 123 | winStreakCount 124 | }; 125 | 126 | const globalResult: GlobalResult = { 127 | stage: { 128 | id: req.params.stageId 129 | }, 130 | selectedColor: req.body.selectedColor, 131 | previousColor: lastGlobalResult ? lastGlobalResult.selectedColor : null, 132 | matched 133 | }; 134 | 135 | // Save player's result 136 | const { resource: savedPlayerResult } = await container.items.create(playerResult); 137 | console.log(savedPlayerResult); 138 | 139 | // Save global result 140 | context.bindings.globalResultsOut = globalResult; 141 | 142 | // Return the result 143 | context.res = { 144 | status: 201, 145 | body: playerResult 146 | }; 147 | 148 | }; 149 | 150 | export default httpTrigger; -------------------------------------------------------------------------------- /arm-templates/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "identifier": { 6 | "type": "string" 7 | }, 8 | "staticWebAppLocation": { 9 | "type": "string", 10 | "defaultValue": "eastasia", 11 | "allowedValues": [ 12 | "centralus", 13 | "eastus2", 14 | "eastasia", 15 | "westeurope", 16 | "westus2" 17 | ] 18 | }, 19 | "staticWebAppSkuTier": { 20 | "type": "string", 21 | "defaultValue": "Free", 22 | "allowedValues": [ 23 | "Free" 24 | ] 25 | }, 26 | "staticWebAppSkuName": { 27 | "type": "string", 28 | "defaultValue": "Free", 29 | "allowedValues": [ 30 | "Free" 31 | ] 32 | }, 33 | "staticWebAppRepositoryUrl": { 34 | "type": "string", 35 | "metadata": { 36 | "description": "Set GitHub repository URL like https://github.com//." 37 | } 38 | }, 39 | "staticWebAppRepositoryToken": { 40 | "type": "securestring", 41 | "metadata": { 42 | "description": "Set GitHub personal access token with public_repo scope." 43 | } 44 | }, 45 | "staticWebAppBranch": { 46 | "type": "string", 47 | "defaultValue": "main" 48 | }, 49 | "staticWebAppAppLocation": { 50 | "type": "string", 51 | "defaultValue": "frontend" 52 | }, 53 | "staticWebAppApiLocation": { 54 | "type": "string", 55 | "defaultValue": "api" 56 | }, 57 | "staticWebAppAppArtifactLocation": { 58 | "type": "string", 59 | "defaultValue": "dist" 60 | }, 61 | "staticWebAppExists": { 62 | "type": "bool", 63 | "defaultValue": false 64 | }, 65 | "cosmosDbEnableFreeTier": { 66 | "type": "bool", 67 | "defaultValue": true 68 | }, 69 | "cosmosDbDatabaseThroughput": { 70 | "type": "int", 71 | "defaultValue": 400 72 | } 73 | }, 74 | "functions": [], 75 | "variables": { 76 | "staticWebAppName": "[concat('static-', parameters('identifier'))]", 77 | "cosmosDbAccountName": "[concat('cosmos-', parameters('identifier'))]", 78 | "cosmosDbDatabaseName": "games", 79 | "cosmosDbContainers": [ 80 | { 81 | "name": "global-results", 82 | "partitionKeyPath": "/stage/id" 83 | }, 84 | { 85 | "name": "player-results", 86 | "partitionKeyPath": "/lineUser/id" 87 | } 88 | ] 89 | }, 90 | "resources": [ 91 | { 92 | "condition": "[not(parameters('staticWebAppExists'))]", 93 | "name": "[variables('staticWebAppName')]", 94 | "type": "Microsoft.Web/staticSites", 95 | "apiVersion": "2020-06-01", 96 | "location": "[parameters('staticWebAppLocation')]", 97 | "properties": { 98 | "repositoryUrl": "[parameters('staticWebAppRepositoryUrl')]", 99 | "branch": "[parameters('staticWebAppBranch')]", 100 | "repositoryToken": "[parameters('staticWebAppRepositoryToken')]", 101 | "buildProperties": { 102 | "appLocation": "[parameters('staticWebAppAppLocation')]", 103 | "apiLocation": "[parameters('staticWebAppApiLocation')]", 104 | "appArtifactLocation": "[parameters('staticWebAppAppArtifactLocation')]" 105 | } 106 | }, 107 | "sku": { 108 | "Tier": "[parameters('staticWebAppSkuTier')]", 109 | "Name": "[parameters('staticWebAppSkuName')]" 110 | } 111 | }, 112 | { 113 | "name": "[variables('cosmosDbAccountName')]", 114 | "type": "Microsoft.DocumentDB/databaseAccounts", 115 | "apiVersion": "2020-04-01", 116 | "location": "[resourceGroup().location]", 117 | "tags": { 118 | }, 119 | "kind": "GlobalDocumentDB", 120 | "properties": { 121 | "consistencyPolicy": { 122 | "defaultConsistencyLevel": "Session" 123 | }, 124 | "locations": [ 125 | { 126 | "locationName": "[resourceGroup().location]", 127 | "failoverPriority": 0 128 | } 129 | ], 130 | "databaseAccountOfferType": "Standard", 131 | "enableFreeTier": "[parameters('cosmosDbEnableFreeTier')]" 132 | }, 133 | "resources": [ 134 | { 135 | "name": "[variables('cosmosDbDatabaseName')]", 136 | "type": "sqlDatabases", 137 | "apiVersion": "2020-04-01", 138 | "location": "[resourceGroup().location]", 139 | "properties": { 140 | "resource": { 141 | "id": "[variables('cosmosDbDatabaseName')]" 142 | }, 143 | "options": { 144 | "throughput": "[parameters('cosmosDbDatabaseThroughput')]" 145 | } 146 | }, 147 | "dependsOn": [ 148 | "[resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmosDbAccountName'))]" 149 | ] 150 | } 151 | ] 152 | }, 153 | { 154 | "name": "[concat(variables('cosmosDbAccountName'), '/', variables('cosmosDbDatabaseName'), '/', variables('cosmosDbContainers')[copyIndex('cosmosDbContainersCopy')].name)]", 155 | "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", 156 | "apiVersion": "2020-04-01", 157 | "properties": { 158 | "resource": { 159 | "id": "[variables('cosmosDbContainers')[copyIndex('cosmosDbContainersCopy')].name]", 160 | "partitionKey": { 161 | "paths": [ 162 | "[variables('cosmosDbContainers')[copyIndex('cosmosDbContainersCopy')].partitionKeyPath]" 163 | ] 164 | } 165 | }, 166 | "options": {} 167 | }, 168 | "dependsOn": [ 169 | "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', variables('cosmosDbAccountName'), variables('cosmosDbDatabaseName'))]" 170 | ], 171 | "copy": { 172 | "name": "cosmosDbContainersCopy", 173 | "count": "[length(variables('cosmosDbContainers'))]" 174 | } 175 | } 176 | ], 177 | "outputs": {} 178 | } -------------------------------------------------------------------------------- /docs/self-paced-handson.md: -------------------------------------------------------------------------------- 1 | # セルフペースドハンズオン 2 | 3 | ## 準備 4 | 5 | 本環境を実行するには下記の準備が必要です。 6 | 7 | | 必要なもの | 説明 | 8 | |----|----| 9 | | LINEアカウント | LINEアカウントをお持ちでない場合は、スマートフォンで[LINEをダウンロード](https://line.me/) し、LINEアカウントを作成してください。 | 10 | | Azureアカウント | Azureアカウントをお持ちでない場合は、[こちら](https://azure.microsoft.com/ja-jp/free/) から作成してください。新規作成すると無料枠が適用されます。 | 11 | | GitHubアカウント | GitHubアカウントをお持ちでない場合は、[こちら](https://github.com/join) から作成してください。 | 12 | 13 | 本ハンズオンで作成するアプリは、LINE のスマートフォンアプリ内で開くLIFFブラウザでの動作を想定しています。 14 | 15 | また、本アプリは Google Chrome や Microsoft Edge などの外部ブラウザで開いた場合も利用できるよう調整していますが、その場合は、LINEアカウントでのログインが求められます。外部ブラウザの場合は、シェア機能は利用できません。 16 | 17 | ## 大まかな流れ 18 | 19 | 1. LINE アカウントで LINEログインのチャネルを作成する 20 | 1. GitHub リポジトリをテンプレートから作成する 21 | 1. GitHub の Personal access token を生成する 22 | 1. Azure Static Web Apps と Cosmos DB をデプロイする 23 | 1. Azure Static Web Apps の Functions の Application settings を設定する 24 | 1. GitHub リポジトリの Secrets を設定する 25 | 1. GitHub リポジトリのワークフローを修正する 26 | 1. GitHub アクションを確認する 27 | 1. LINE DevelopersコンソールでエンドポイントURLを設定する 28 | 1. 動作確認をする 29 | 30 | ## LINE アカウントで LINEログインのチャネルを作成する 31 | 32 | まず、[LINE Developersコンソール](https://developers.line.biz/console/)にログインし、プロバイダーと「LINEログイン」のチャネルを作成します。 33 | 34 | LINE Developersコンソールを開機、プロバイダーの「作成」ボタンを選択します。 35 | 36 | ![](./images/line-develpers-console_create-provider_001.png) 37 | 38 | 任意のプロバイダー名を入力し、「作成」ボタンを選択し作成します。 39 | 40 | ![](./images/line-develpers-console_create-provider_002.png) 41 | 42 | プロバイダーが作成できたら、「チャネル設定」のタブで「LINEログイン」を選択します。 43 | 44 | ![](./images/line-develpers-console_create-channel_001.png) 45 | 46 | 下記を入力し、「作成」ボタンを選択しチャネルを作成します。 47 | 48 | - 「チャネル名」「チャネル説明」を適宜入力してください。 49 | - 「アプリタイプ」は「ウェブアプリ」「ネイティブアプリ」ともにチェックを付けてください。 50 | - 「LINE Developers Agreement の内容に同意します」の Agreement の内容を確認の上、チェックを付けてください。 51 | 52 | ![](./images/line-develpers-console_create-channel_002.png) 53 | 54 | チャネルが作成できたら、「LIFF」タブを開き、「追加」ボタンを選択します。 55 | 56 | ![](./images/line-devepolers-console_liff-setting_001.png) 57 | 58 | 下記を入力し、「追加」ボタンを選択しLIFFアプリを追加します。 59 | 60 | - 「LIFFアプリ」に任意のアプリ名を入力します。 61 | - 「サイズ」は「Full」を選択します。 62 | - 「エンドポイントURL」は、Azure Static Web Apps をデプロイした後に決まるので、とりいそぎ別の https で始まるURLを入力します。(例: `https://example.com` ) 63 | - 「Scope」は、「profile」にチェックを付けます。 64 | - 「ボットリンク機能」は、「On (Normal)」をチェックします。 65 | 66 | ![](./images/line-devepolers-console_liff-setting_002.png) 67 | 68 | LIFFアプリを追加すると、「LIFF ID」が発行されます。のちに使うので、控えておいてください。 69 | 70 | ![](./images/line-devepolers-console_liff-setting_003.png) 71 | 72 | ## GitHub リポジトリをテンプレートから作成する 73 | 74 | つぎに、テンプレートをもとに、ご自身が操作する GitHub リポジトリを用意します。 75 | 76 | [本リポジトリ](https://github.com/dzeyelid/line-liff-with-azure-handson) のトップページから、上部の「Use this tempalte」ボタンを選択し、このリポジトリテンプレートをベースにリポジトリを作成してください。 77 | 78 | ![](./images/github-repository_create-repository-from-template_001.png) 79 | 80 | 下記を入力し、「Create repository from template」ボタンを選択しリポジトリを作成します。 81 | 82 | - 「Repository name」に、任意のリポジトリ名を入力します。 83 | - 「Public」を選択します。 84 | - 「Include all branches」はチェックしません。 85 | 86 | ![](./images/github-repository_create-repository-from-template_002.png) 87 | 88 | ## GitHub の Personal access token を生成する 89 | 90 | 後述のデプロイで利用するため、GitHub の Personal access token を生成します。 91 | 92 | 下記の手順をもとに進み、 `public_repo` に設定をして、画面下部の「Generate token」ボタンを選択し、トークンを生成します。 93 | 94 | - [個人アクセストークンを使用する - GitHub Docs](https://help.github.com/ja/github/authenticating-to-github/creating-a-personal-access-token) 95 | 96 | ![](./images/github_develper-settings_generate-personal-access-token_001.png) 97 | 98 | 生成されたトークンはここでしか表示されないので、適宜控えてください。 99 | 100 | ![](./images/github_develper-settings_generate-personal-access-token_002.png) 101 | 102 | ## Azure Static Web Apps と Cosmos DB をデプロイする 103 | 104 | Azure Static Web Apps と Azure Cosmos DB をデプロイします。 105 | 106 | 下記のボタンを選択すると、Azure ポータルで「Costom deployment」画面が開きます。 107 | 108 | [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fdzeyelid%2Fline-liff-with-azure-handson%2Fmain%2Farm-templates%2Ftemplate.json) 109 | 110 | 下記を入力し、「Review + create」ボタンを選択して入力内容を表示します。 111 | 112 | - 「Resource group」は、「Create new」を選択し、表示されたダイアログに任意のリソースグループ名を入力し、「OK」を選択します。 113 | - 「Region」は、近いリージョン(`Japan East` や `Japan West` など)を入力します。 114 | - 「Identifier」は、グローバルで一位になるような文字列を入力します。(※ この文字列を使う Cosmos DB のリソース名がグローバルで一意になる必要があります。) 115 | - 「Static Web App Location」は、近いリージョンを指定します。日本からは `eastasia` のままで構いません。 116 | - 「Static Web App Sku Tier」および「Static Web App Sku Name」は、「Free」のままで構いません。 117 | - 「Static Web App Repository Url」は、先ほど作成した GitHub リポジトリのURLを設定します。(例: `https://github.com//` ) 118 | - 「Static Web App Repository Token」は、先ほど生成した GitHub persional access token を設定します。 119 | - 「Static Web App Branch」「Static Web App App Location」「Static Web App Api Location」「Static Web App App Artifact Location」は変更しません。 120 | - 「Static Web App Exists」「Cosmos Db Enable Free Tier」「Cosmos Db Database Throughput」はそのままで構いません。 121 | 122 | ![](./images/azure-portal_deployment_001.png) 123 | ![](./images/azure-portal_deployment_002.png) 124 | 125 | 入力内容を確認し、「Create」ボタンを選択しデプロイを実行します。 126 | 127 | ![](./images/azure-portal_deployment_003.png) 128 | 129 | デプロイが完了するまでしばらく時間がかかります。 130 | 131 | デプロイが完了すると下記の画面に遷移します。「Go to resource group」ボタンを選択し、作成したリソースグループに移動します。 132 | 133 | ![](./images/azure-portal_deployment_004.png) 134 | 135 | ![](./images/azure-portal_resource-group.png) 136 | 137 | ## Azure Static Web Apps の Functions の Application settings を設定する 138 | 139 | Azure Static Web Apps の Functions の Application settings の設定を行います。 140 | 141 | このうち Cosmos DB の接続文字列が必要なので、先に取得しておきましょう。 142 | 143 | まず、リソースグループのリソース一覧から Cosmos DB を選択し、開きましょう。 144 | 145 | ![](./images/azure-portal_get-cosmosdb-connstring_001.png) 146 | 147 | Cosmos DB の画面で、左メニューから「Keys」を選択します。 148 | 149 | ![](./images/azure-portal_get-cosmosdb-connstring_002.png) 150 | 151 | Cosmos DB のキー及び接続文字列が表示されます。「Read-write Keys」タブの「PRIMARY CONNECTION STRING」の値をコピーし控えておきましょう。 152 | 153 | ![](./images/azure-portal_get-cosmosdb-connstring_003.png) 154 | 155 | それでは、Static Web Apps の設定に進みましょう。 156 | 157 | まず、リソースグループの画面に戻ります。前述の画面空移動するには、画面上部のリソースグループ名の部分を選択すると、リソースグループに遷移できます。 158 | 159 | ![](./images/azure-portal_get-cosmosdb-connstring_004.png) 160 | 161 | リソースグループの画面で、Static Web Apps を選択し開きます。 162 | 163 | ![](./images/azure-portal_static-web-apps_set-func-app-settings_001.png) 164 | 165 | 左のメニューの「Settings」から「Configuration」を開きます。ここでは、各 Environment に対して、Application settings として環境変数を設定できます。 166 | 167 | ![](./images/azure-portal_static-web-apps_set-func-app-settings_002.png) 168 | 169 | 下記 4つ環境変数を登録します。「+ Add」を選択し、一つずつ登録していきましょう。 170 | 171 | | Variable name | value | 172 | |----|----| 173 | | `COSMOS_DB_CONNECTION_STRING` | Cosmos DB の接続文字列(Connection string) | 174 | | `COSMOS_DB_CONTAINER_ID_GLOBAL_RESULTS` | `global-results` | 175 | | `COSMOS_DB_CONTAINER_ID_PLAYER_RESULTS` | `player-results` | 176 | | `COSMOS_DB_DATABASE_ID` | `games` | 177 | 178 | ![](./images/azure-portal_static-web-apps_set-func-app-settings_003.png) 179 | 180 | 4つの環境変数を登録し終えたら、忘れずに「Save」ボタンを選択し保存してください。 181 | 182 | ![](./images/azure-portal_static-web-apps_set-func-app-settings_004.png) 183 | 184 | ## GitHub リポジトリの Secrets を設定する 185 | 186 | GitHub Actions のワークフローによる Azure Static Web Apps のデプロイに必要な Secret を設定します。 187 | 188 | 作成した GitHub リポジトリの「Settings」から「Secrets」を開きましょう。 189 | 190 | ![](./images/github_secrets_001.png) 191 | 192 | すると、すでにひとつ `AZURE_STATIC_WEB_APPS_API_TOKEN_XXXX_XXXX_123456789` というような secret が作成されていることがわかります。これは、Azure Static Web Apps のリソースをデプロイしたときに自動的に作成された secret です。ワークフローはこの secret を用ることによってコードを Azure Static Web Apps にデプロイできるようになります。 193 | 194 | ![](./images/github_secrets_002.png) 195 | 196 | もうひとつ secret を追加しましょう。 197 | 198 | 「New secret」ボタンを選択し、secret 作成画面を開きます。 199 | 200 | ![](./images/github_secrets_003.png) 201 | 202 | 「Name」に `LIFF_ID` を指定し、「Value」に前の手順で控えた「LIFF ID」を指定し、「Add secret」ボタンを選択して保存します。 203 | 204 | ![](./images/github_secrets_004.png) 205 | 206 | ## GitHub リポジトリのワークフローを修正する 207 | 208 | 次に、ワークフローを修正します。GitHub のリポジトリの `.github/workflows` ディレクトリを開きましょう。 209 | 210 | ![](./images/github_edit-workflow_001.png) 211 | 212 | `.github/workflows` には、2つのワークフローがあります。 213 | 214 | このうち、 `deploy-azure-static-web-apps.yml` は元リポジトリで使用していたものなので、削除しておきましょう。 215 | 216 | ![](./images/github_edit-workflow_002.png) 217 | 218 | `deploy-azure-static-web-apps.yml` を開き、ごみ箱のマークを選択しファイルを削除します。 219 | 220 | ![](./images/github_edit-workflow_003.png) 221 | 222 | ここでは、適宜コミットタイトルとディスクリプションを入力し、「Commit changes」ボタンを選択し、削除をコミットします。なお、実際のコミットの作法は各自・各プロジェクトに従ってください。 223 | 224 | ![](./images/github_edit-workflow_004.png) 225 | 226 | つぎに、もう一方の `azure-static-web-apps-xxx-xxx-123456789.yml` というようなファイル名のワークフローは、Azure Static Web Apps のデプロイ時に生成されたものです。(以降、`azure-static-web-apps-.yml` と表記します。) 227 | 228 | ![](./images/github_edit-workflow_005.png) 229 | 230 | この `azure-static-web-apps-.yml` をみてみましょう。 231 | 232 | すでに `Azure/static-web-apps-deploy@v0.0.1-preview` という Azure Static Web Apps にデプロイするための GitHub アクションが設定されていることがわかります。 233 | 234 | そして、このアクションのパラメータ `azure_static_web_apps_api_token` には、先ほど確認した secret `AZURE_STATIC_WEB_APPS_API_TOKEN_XXXX_XXXX_123456789` が利用されていることもわかります。 235 | 236 | さて、本アプリを正しくデプロイするために一部編集します。 237 | 238 | GitHub で扱いファイルを編集するには多くの方法がありますが、ここでは GitHub 上で直接編集します。ファイルの右上にある鉛筆のマーク(Edit this file という注釈)を選択して、編集画面に遷移します。 239 | 240 | ![](./images/github_edit-workflow_006.png) 241 | 242 | GitHub 上でワークフローファイルを編集するときは、このように GitHub アクションが編集しやすいUIを利用できます。 243 | 244 | ![](./images/github_edit-workflow_007.png) 245 | 246 | 「Azure/static-web-apps-deploy」アクションの実行時に、環境変数として `VUE_APP_LIFF_ID` を渡したいので、下記の設定を追加します。 247 | 248 | ```yml 249 | env: 250 | VUE_APP_LIFF_ID: ${{ secrets.LIFF_ID }} 251 | ``` 252 | 253 | 追加する位置は、 `with:` と同階層です。 254 | 255 | ```diff 256 | - name: Build And Deploy 257 | id: builddeploy 258 | uses: Azure/static-web-apps-deploy@v0.0.1-preview 259 | with: 260 | ... 261 | ###### End of Repository/Build Configurations ###### 262 | + env: 263 | + VUE_APP_LIFF_ID: ${{ secrets.LIFF_ID }} 264 | ``` 265 | 266 | ![](./images/github_edit-workflow_008.png) 267 | 268 | GitHubアクションの利用については、こちらをご参考ください。 269 | 270 | - [GitHub Actions 入門 - GitHub Docs](https://docs.github.com/ja/free-pro-team@latest/actions/learn-github-actions/introduction-to-github-actions) 271 | 272 | また、「Azure/static-web-apps-deploy」アクションの詳細はこちらをご参照ください。 273 | 274 | - [Azure Static Web Apps Deploy · Actions · GitHub Marketplace](https://github.com/marketplace/actions/azure-static-web-apps-deploy) 275 | 276 | ファイルの編集ができたら、コミットします。ここでは、「Start commit」ボタンを選択し、コミットタイトルやディスクリプションを適宜入力の上、「Commit changes」ボタンを選択しコミットします。なお、実際のコミットの作法は各自・各プロジェクトに従ってください。 277 | 278 | ![](./images/github_edit-workflow_009.png) 279 | 280 | ## GitHub アクションを確認する 281 | 282 | コミットを終えると、このワークフローが実行されます。( `on.push.branches: [main]` のトリガが設定されているため) 283 | 284 | 実行内容を見てみましょう。「Actions」タブを開くとこれまでに実行されたワークフローの一覧が表示されます。 285 | 286 | 実行中のワークフロー(黄色いマーク)を開いてみましょう。 287 | 288 | ![](./images/github_workflow_001.png) 289 | 290 | 「Buid and Deploy Job」を開くと各ステップを見ることができます。「Build And Deploy」を開くと、Azure Static Web Apps へコードをデプロイする様子を確認することができます。 291 | 292 | ![](./images/github_workflow_002.png) 293 | 294 | 下の方までスクロールし、デプロイが成功していると、このようにデプロイされたURL `https://.azurestaticapps.net/` が表示されます。(URLは各環境で異なります。) 295 | 296 | ![](./images/github_workflow_003.png) 297 | 298 | このURLをコピーして控えておいてください。 299 | 300 | ## LINE DevelopersコンソールでエンドポイントURLを設定する 301 | 302 | URLを開く前に、最後の仕上げがあります。 303 | 304 | [LINE Developersコンソール](https://developers.line.biz/console/) にもどり、作成したLIFFアプリの詳細画面に戻ります。 305 | 306 | 「エンドポイントURL」の「編集」ボタンを選択し、先ほど表示されていた Azure Static Web Apps のURLに置き換えます。「更新」ボタンを選択し、保存しましょう。 307 | 308 | ![](./images/line-devepolers-console_liff-setting_004.png) 309 | 310 | ## 動作確認をする 311 | 312 | ここまで完了しましたら、LIFF URL を開いてみましょう!LINEアプリがインストールされているスマートフォン、あるいは外部ブラウザでURLを開きます。 313 | 314 | ![](./images/line-devepolers-console_liff-setting_005.png) 315 | 316 | スマートフォンで開いた場合は、LINEアプリが起動しこのように表示されます。 317 | 318 | ![](./images/liff-app_on-line_001.png) 319 | 320 | 321 | 「Allow」を選択し、許可することで LIFFアプリを利用できるようになります。 322 | 323 | ![](./images/liff-app_on-line_002.png) 324 | 325 | 外部ブラウザで開いた場合は、LINEログインを促され、ログインすると LIFF アプリが表示されます。 326 | 327 | ![](./images/liff-app_on-web_001.png) 328 | 329 | ![](./images/liff-app_on-web_002.png) 330 | 331 | Congratulation!! :tada: 332 | --------------------------------------------------------------------------------