- 投稿日:2022-02-23T22:15:00+09:00
ワイ「Denoってなんぞや?」
?「やあ」 ワイ「前回はNode.jsについて知見を得たな」 ワイ「どんどん最強ぷよぐらまに近づいてまうで^^」 ?「やあ」 ワイ「急になんやこの恐竜」 ワイ「某J民か?」 ?「あんなのと一緒にしないでよ」 ?「僕はDenoだよ」 ワイ「誰だよ(ピネ」 ?「Node.jsの親戚みたいなものさ」 ワイ「Node.jsくんにも親戚がおったんか」 ワイ「初耳やわ(ハナホジ」 ?「Denoについて」 ?「Node.jsのついでに知ってもらおうと思って」 ?「今ここにいるんだ」 ワイ「ほーん しゃあない聞いたるわ」 ?「......」 ?「Deno(ディーノ) はRyan Dahlによって作られた比較的新しいJavascript/Typescriptランタイムだよ」 ワイ「誰やその人」 ?「Node.js を作った人だよ」 ワイ「なんやおんなじ人か」 ワイ「だから親戚なんやな」 ?「ちなみにDenoはNodeのアナグラムだよ」 ワイ「でもNode.jsがすでにあるのにわざわざ新しいのを作ったんや?」 ?「それはRyan DahlがNode.jsの開発において10個の後悔があって」 ?「その反省を活かして開発されたのがDenoなんだ」 ワイ「完璧主義やったんやろか」 ワイ「とりあえず使ってみるか」 ワイ「とりあえず使ってみるで」 ?「インストールはここに書いてるよ」 ワイ「ワイはWindowsを使ってるからPowerShellからインスコすればええんやな」 ワイ「楽な時代になったもんやで^^」 PowerShell iwr https://deno.land/install.ps1 -useb | iex ワイ「これやな」 ワイ「インスコ完了や」 ?「まずはサンプルを実行してみよう!」 PowerShell deno run https://deno.land/std/examples/welcome.ts ?「コマンドはこれだよ」 ワイ「オンライン上のファイルをそのまま実行できるんか便利やな」 ワイ「実行してみたで」 PowerShell Download https://deno.land/std/examples/welcome.ts Warning Implicitly using latest version (0.126.0) for https://deno.land/std/examples/welcome.ts Download https://deno.land/std@0.126.0/examples/welcome.ts Check https://deno.land/std/examples/welcome.ts Welcome to Deno! ?「無事成功だね」 ?「僕の特徴について」 ?「ここまでで早速Denoの特徴があったんだけどなにかわかる?」 ワイ「わからん(即答」 ?「......」 ?「正解はデフォルトでTypescriptに対応してる点だよ」 ワイ「確かに実行したファイルの拡張子は .tsになってるな」 ワイ「Node.jsでのTypescript開発やったらパッケージ必須やったもんな」 ?「まだまだあるよ」 ?「このコードを実行してみて」 test.ts import { serve } from "https://deno.land/std@0.126.0/http/server.ts"; console.log("http://localhost:8000/"); serve((req) => new Response("Hello World\n"), { port: 8000 }); PowerShell deno run .\test.ts PowerShell 結果 Download https://deno.land/std@0.126.0/http/server.ts Download https://deno.land/std@0.126.0/async/mod.ts Download https://deno.land/std@0.126.0/async/deadline.ts Download https://deno.land/std@0.126.0/async/debounce.ts Download https://deno.land/std@0.126.0/async/deferred.ts Download https://deno.land/std@0.126.0/async/delay.ts Download https://deno.land/std@0.126.0/async/mux_async_iterator.ts Download https://deno.land/std@0.126.0/async/pool.ts Download https://deno.land/std@0.126.0/async/tee.ts Check file:///C:/Users/kazum/Desktop/programings/typescript/dist.ts http://localhost:8000/ ⚠️ ️Deno requests net access to "0.0.0.0:8000". Run again with --allow-net to bypass this prompt. Allow? [y/n (y = yes allow, n = no deny)] y ワイ「なんか途中で許可を求めてきたな」 ?「それが特徴デフォルトでファイルやネットワークにアクセス出来ないだよ」 ワイ「まあセキュリティが高なってるって訳やな」 ワイ「ていうかimportもurlからになってるんやな」 ?「それも大きな特徴の一つnpm・requireの廃止だよ」 ?「わざわざnpm installをする必要がないんだ」 ワイ「それは便利な気がするなぁ」 ワイ「無事Hello Worldしてくれたな」 ワイ「まとめやで」 ワイ「また一つ賢くなってしまったわ^^」 ワイ「今後はDenoがNode.jsを追い越したりするんかいな」 ?「どうだろうね」 ワイ「いつか開発する機会があればDenoで開発しよかな」 ?「......(絶対やらないじゃん」
- 投稿日:2022-02-23T20:41:15+09:00
クリップボードを共有するPWAアプリの作成
WindowsとAndroidでテキストを共有したいことがよくあり、最近はMacやiPhoneとも共有することが増えてきたので、クリップボードのテキストを共有するPWAアプリを作成します。 PWAにすれば、アプリっぽく使えるのと、後程示すショートカットが便利だったりします。 以下のGitHubにソースコードを上げておきます。 poruruba/ClipShare テキストの共有方法 単に、Node.jsサーバを立ち上げて、テキストを保持するようにし、HTTP Post呼び出して、Set/Getするようにしているだけです。 ソースコードを載せますが、大したことはやっていません。 一応、セキュリティを考慮して、APIKeyがあっていないと受け付けないようにするのと、1日間以上はテキストを保持しないようにしています。以下に示す変数APIKEYの部分です。 /api/controllers/clipshare-api/index.js 'use strict'; const HELPER_BASE = process.env.HELPER_BASE || "/opt/"; const Response = require(HELPER_BASE + 'response'); const APIKEY = "【任意のAPIKey】"; const EXPIRES_IN = 60 * 60 * 1000; var clip = null; exports.handler = async (event, context, callback) => { var body = JSON.parse(event.body); if( event.requestContext.apikeyAuth.apikey != APIKEY ) throw "apikey invalid"; switch(event.path){ case "/clipshare-get":{ if( !clip || new Date().getTime() > clip.created_at + EXPIRES_IN ) return new Response({status: "ng"}); return new Response({ status: "ok", clip: clip }); } case "/clipshare-set":{ clip = { text: body.text, created_at: new Date().getTime() }; return new Response({ status: "ok" } ); } } }; ※セキュリティ上の配慮はないので、自己責任でお願いします。 ※それから、HTTPSで通信するようにしないと、テキストの内容は漏洩します。 クライアント側 PWA化するために、マニフェストファイルを作成します。 /public/clipshare/manifest.json { "short_name": "クリップシェア", "name": "クリップシェア", "display": "standalone", "start_url": "index.html", "icons": [ { "src": "img/192x192.png", "sizes": "192x192" } ], "shortcuts": [ { "name": "Clip⇒Upload", "description": "クリップボードをアップロードします。", "url": "/clipshare/index.html?cmd=clip2upload", "icons": [ { "src": "img/192x192.png", "sizes": "192x192" } ] }, { "name": "Download⇒Clip", "description": "ダウンロードしてクリップボードにコピーします。", "url": "/clipshare/index.html?cmd=download2clip", "icons": [ { "src": "img/192x192.png", "sizes": "192x192" } ] } ] } 少し補足しますと、「shortcuts」という項目がありますが、これは、PWAとしてインストールした後に、右クリックまたは長押しで、ワンタッチでクリップボード上のテキストをサーバにアップしたり、サーバ上のテキストをクリップボードにコピーするためのショートカットの設定です。ホーム画面にショートカットとしてもおけるのがうれしい。 HTMLで指定します。 /public/clipshare/index.html <link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.webmanifest" /> <script async src="https://cdn.jsdelivr.net/npm/pwacompat" crossorigin="anonymous"></script> また、PWAのために、ServiceWorkerを立ち上げる必要があるため、以下のJSを作成します。 /public/clipshare/sw.js var CACHE_NAME = 'clipshare-pwa-caches'; self.addEventListener('fetch', function(event) { console.log('sw event: fetch called'); }); あとは、これを、ページロード後に読み出すようにします。 /public/clipshare/js/start.js if ('serviceWorker' in navigator) { navigator.serviceWorker.register('sw.js').then(async (registration) => { console.log('ServiceWorker registration successful with scope: ', registration.scope); }).catch((err) => { console.log('ServiceWorker registration failed: ', err); }); } PWAについては、以下を参考にしてください。 PWAを試してみよう クリップボードのコピー/ペースト 以下の通りです。 /public/clipshare/js/start.js clip_paste: async function(){ return navigator.clipboard.readText(); }, clip_copy: async function(text){ return navigator.clipboard.writeText(text); }, async/awaitなので注意です。 参考:Clipboard API https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API クライアント側ソースコード Javascriptソースを以下に示します。 /public/clipshare/js/start.js 'use strict'; //const vConsole = new VConsole(); //window.datgui = new dat.GUI(); var vue_options = { el: "#top", mixins: [mixins_bootstrap], data: { dialog_input: {}, base_url: "", apikey: "", payload: "", message: "", }, computed: { }, methods: { clipshare_get: async function(){ try{ this.message = ""; var json = await do_post_with_apikey(this.base_url + "/clipshare-get", {}, this.apikey); if( json.clip ){ this.payload = json.clip.text; await this.clip_copy(this.payload); this.message = "DOWNLOAD created_at " + new Date(json.clip.created_at).toLocaleString( 'ja-JP', {} ); }else{ this.message = "NO Clip Data"; } }catch(error){ alert(error); } }, clipshare_set: async function(){ try{ this.message = ""; var params = { text: this.payload }; await do_post_with_apikey(this.base_url + "/clipshare-set", params, this.apikey); this.message = "UPLOAD at " + new Date().toLocaleString( 'ja-JP', {} ); }catch(error){ alert(error); } }, clipshare_paste: async function(){ this.payload = await this.clip_paste(); this.message = "clipboard PASTE"; }, clipshare_copy: async function(){ await this.clip_copy(this.payload); this.message = "clipboard COPY"; }, clipshare_clear: function(){ this.message = ""; this.payload = ""; }, set_apikey: function(){ this.dialog_input = { base_url: this.base_url || "", apikey: this.apikey || "" }; this.dialog_open('#apikey_dialog'); }, apkey_update: function(){ localStorage.setItem("base_url", this.dialog_input.base_url); localStorage.setItem("apikey", this.dialog_input.apikey); this.dialog_close('#apikey_dialog'); alert('設定しました。リロードしてください。'); } }, created: function(){ }, mounted: async function(){ proc_load(); if ('serviceWorker' in navigator) { navigator.serviceWorker.register('sw.js').then(async (registration) => { console.log('ServiceWorker registration successful with scope: ', registration.scope); }).catch((err) => { console.log('ServiceWorker registration failed: ', err); }); } this.base_url = localStorage.getItem('base_url'); this.apikey = localStorage.getItem('apikey'); if( this.apikey ){ switch(searchs.cmd){ case 'clip2upload':{ this.payload = await this.clip_paste(); await this.clipshare_set(); alert('クリップボードをアップロードしました。'); window.close(); break; } case 'download2clip':{ await this.clipshare_get() alert('ダウンロードしてクリップボードにコピーしました。'); window.close(); break; } default:{ this.clipshare_get(); break; } } }else{ setTimeout( () =>{ alert('API Keyを指定してください。'); }, 0); } } }; vue_add_data(vue_options, { progress_title: '' }); // for progress-dialog vue_add_global_components(components_bootstrap); vue_add_global_components(components_utils); /* add additional components */ window.vue = new Vue( vue_options ); function do_post_with_apikey(url, body, apikey) { const headers = new Headers({ "Content-Type": "application/json; charset=utf-8", "X-API-KEY": apikey }); return fetch(url, { method: 'POST', body: JSON.stringify(body), headers: headers }) .then((response) => { if (!response.ok) throw 'status is not 200'; return response.json(); // return response.text(); // return response.blob(); // return response.arrayBuffer(); }); } セットアップ 以下から、ZIPでダウンロードします。 その後、適当なフォルダで展開して、npm install; node app.js; でOKです。 HTTPSにするには、certフォルダを作ってそこにSSL証明書を置いてください。 クライアント側では最初に立ち上げたサーバのURLとAPIKeyを設定してください。 以上
- 投稿日:2022-02-23T19:58:01+09:00
imagemin-mozjpegのバージョンを最新にしたらrequire()のエラーが出てはまった
事の発端は軽い気持ちでバージョンを上げただけ 1年ぶりぐらいに手元の開発環境をアップグレードしようと思い何も考えずにバージョンをアップデートしたことが始まりでした。 imageminのビルド時だけエラーが出始める エラーの内容を適当にコピペして海外サイトとかを徘徊したが、原因が掴めず・・・。 const imageminMozjpeg = require('imagemin-mozjpeg') ^ Error [ERR_REQUIRE_ESM]: require() of ES Module /Users/techthree/projects/tech-three/pug_sass_webpack/node_modules/imagemin-mozjpeg/index.js from /Users/techthree/projects/tech-three/pug_sass_webpack/imagemin.js not supported. Instead change the require of index.js in /Users/techthree/projects/tech-three/pug_sass_webpack/imagemin.js to a dynamic import() which is available in all CommonJS modules. at Object.<anonymous> (/Users/techthree/projects/tech-three/pug_sass_webpack/imagemin.js:2:25) { code: 'ERR_REQUIRE_ESM' } imageminは今から2ヶ月前にアップデートされていた npmimagemin-mozjpegのバージョンによってimportかrequireか異なっていました。 最新の10系はimport、9系はrequireとなっていました。 ひとまず最新を使用したいのでimportに置換します。 import imagemin from 'imagemin'; import imageminMozjpeg from 'imagemin-mozjpeg'; (async () => { await imagemin(['src/assets/img/**/*.jpg'], { destination: 'build/images', plugins: [ imageminMozjpeg() ] }); console.log('Images optimized'); })(); importにしたら違うエラーが出た!!! (node:21109) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension. (Use `node --trace-warnings ...` to show where the warning was created) /Users/techthree/projects/tech-three/pug_sass_webpack/imagemin.js:1 import imagemin from 'imagemin'; ^^^^^^ エラーの内容を注視する!!! ビルド時のエラーメッセージに対処法が記載されていたのでその通りに対処してみます。 この場合はpackage.jsonに "type": "module" を追加します。 私はscriptsの直前に指定しました。 { "name": "npmscripts", "version": "1.0.0", "description": "", "main": "index.js", "type": "module", "scripts": { "watch:imagemin": "onchange src/img/**/*.{jpg,png} -- node imagemin.js {{changed}}", "build:imagemin": "node imagemin.js" }, "author": "Tech Three", "license": "ISC", "devDependencies": {} } 追加後に再度ビルドを実行するとエラーが出ずに正常に完了することができました! yarn run v1.22.17 $ node imagemin.js Images optimized ✨ Done in 0.42s.
- 投稿日:2022-02-23T00:05:34+09:00
AWS API Gateway - IAM アクセス許可により API へのアクセスを制御する
はじめに lambda や API の実態と認証のロジックを切り離したい。 そしたらもっとシンプルにバックエンド開発ができる。 API はすべて API Gateway を通る構成なので、API Gateway をリバースプロキシのように扱い、そこでアクセス制御をしたい。 API Gateway で IAM アクセス制御を設定すると... AWS サービスがリクエストを受信すると、リクエストで送信した署名を計算したときと同じステップが実行されます。続いて AWS は、計算された署名とそのリクエストで送信した署名を比較します。署名が一致すると、リクエストが処理されます。署名が一致しない場合、リクエストは拒否されます。 この「署名」を作る処理がとても面倒だったので、メモを残しておきます。 準備 1. API Gateway で認可の設定を 「AWS IAM」 に設定する 2. API Gateway へのアクセス権限をもつ IAM ユーザーを用意する プログラムからアクセスするので、ユーザーは 「プログラムによるアクセス」 にチェック ポリシーの設定 著名をつけずに実行しても {"message":"Missing Authentication Token"} のように、リクエストが拒否される。 実装 最初は こちら を参考に著名を作成しようと思ったが、typescript だといまいちだったので、別の方法を模索。 どうやら AWS SDK for JavaScript の v3 だと著名作成の処理が公開されているようなので、それを使用。 以下の記事をからほぼ流用させてもらった。 実際に扱うなら、記事のコードをそのまま使用したほうが良い。(というか実践的で完成度高いです。) なかなか難しかったので自分なりに砕いたのが下記のコード。 import { SignatureV4 } from "@aws-sdk/signature-v4"; import { Sha256 } from "@aws-crypto/sha256-js"; import { HttpRequest } from "@aws-sdk/protocol-http"; import axios from "axios"; const accessKeyId = "IAMのアクセスキーID"; // process.env.IAM_ACCESS_KEY_ID! const secretAccessKey = "IAMのシークレットアクセスキー"; // process.env.IAM_SECRET_ACCESS_KEY_ID! const hostname = "xxxxxxx.execute-api.ap-northeast-1.amazonaws.com"; // 著名を作るメソッド async function signRequest( headers: Record<string, string>, method: string, path: string, body?: unknown ) { const request = new HttpRequest({ body: body ? JSON.stringify(body) : undefined, headers, hostname: hostname, method: method.toUpperCase(), path, }); const signer = new SignatureV4({ credentials: { accessKeyId, secretAccessKey, }, service: "execute-api", region: "ap-northeast-1", sha256: Sha256, }); const signedRequest = await signer.sign(request); return signedRequest; } async function main() { const headers: Record<string, string> = { host: hostname, "Content-Type": "application/json", }; const path = "/hello"; const data = { name: "tubakuro", }; // 著名(ヘッダー)を作成して const req = await signRequest(headers, "POST", path, data); // 著名と同じ内容でリクエスト const resp = await axios.post(`https://${hostname}${path}`, data, { headers: req.headers, }); console.dir(resp.data); } main(); 以下のようなリクエストが作られて、リクエストが成功したらOK POST /hello HTTP/1.1 x-amz-date: 20220222T135041Z x-amz-content-sha256: xxxxxxxxxxxxxxxxxxxxxxxxxxxxx authorization: AWS4-HMAC-SHA256 Credential=xxx, SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date, Signature=xxx Content-Length: xx Content-Type: application/json Host: xxxxxxx.execute-api.ap-northeast-1.amazonaws.com [body] GET の場合だとこう。 axios GET時の仕様がややこい。 async function main() { const headers: Record<string, string> = { host: hostname, }; const path = "/hello"; const req = await signRequest(headers, "GET", path); const resp = await axios.get(`https://${hostname}${path}`, { headers: req.headers, }); console.dir(resp.data); } まとめ 一度作ってしまえば楽ちんですね ? AWS SDK for JavaScript v3 のリファレンス置いておきます。
- 投稿日:2022-02-23T00:05:34+09:00
AWS API Gateway - IAM アクセス許可により API へのアクセスを制御する(バックエンドサービス間通信)
はじめに lambda や API の実態と認証のロジックを切り離したい。 そしたらもっとシンプルにバックエンド開発ができる。 API はすべて API Gateway を通る構成なので、API Gateway をリバースプロキシのように扱い、そこでアクセス制御をしたい。 API Gateway で IAM アクセス制御を設定すると... AWS サービスがリクエストを受信すると、リクエストで送信した署名を計算したときと同じステップが実行されます。続いて AWS は、計算された署名とそのリクエストで送信した署名を比較します。署名が一致すると、リクエストが処理されます。署名が一致しない場合、リクエストは拒否されます。 この「署名」を作る処理がとても面倒だったので、メモを残しておきます。 準備 1. API Gateway で認可の設定を 「AWS IAM」 に設定する 2. API Gateway へのアクセス権限をもつ IAM ユーザーを用意する プログラムからアクセスするので、ユーザーは 「プログラムによるアクセス」 にチェック ポリシーの設定 著名をつけずに実行しても {"message":"Missing Authentication Token"} のように、リクエストが拒否される。 実装 最初は こちら を参考に著名を作成しようと思ったが、typescript だといまいちだったので、別の方法を模索。 どうやら AWS SDK for JavaScript の v3 だと著名作成の処理が公開されているようなので、それを使用。 以下の記事をからほぼ流用させてもらった。 実際に扱うなら、記事のコードをそのまま使用したほうが良い。(というか実践的で完成度高いです。) なかなか難しかったので自分なりに砕いたのが下記のコード。 import { SignatureV4 } from "@aws-sdk/signature-v4"; import { Sha256 } from "@aws-crypto/sha256-js"; import { HttpRequest } from "@aws-sdk/protocol-http"; import axios from "axios"; const accessKeyId = "IAMのアクセスキーID"; // process.env.IAM_ACCESS_KEY_ID! const secretAccessKey = "IAMのシークレットアクセスキー"; // process.env.IAM_SECRET_ACCESS_KEY_ID! const hostname = "xxxxxxx.execute-api.ap-northeast-1.amazonaws.com"; // 著名を作るメソッド async function signRequest( headers: Record<string, string>, method: string, path: string, body?: unknown ) { const request = new HttpRequest({ body: body ? JSON.stringify(body) : undefined, headers, hostname: hostname, method: method.toUpperCase(), path, }); const signer = new SignatureV4({ credentials: { accessKeyId, secretAccessKey, }, service: "execute-api", region: "ap-northeast-1", sha256: Sha256, }); const signedRequest = await signer.sign(request); return signedRequest; } async function main() { const headers: Record<string, string> = { host: hostname, "Content-Type": "application/json", }; const path = "/hello"; const data = { name: "tubakuro", }; // 著名(ヘッダー)を作成して const req = await signRequest(headers, "POST", path, data); // 著名と同じ内容でリクエスト const resp = await axios.post(`https://${hostname}${path}`, data, { headers: req.headers, }); console.dir(resp.data); } main(); 以下のようなリクエストが作られて、リクエストが成功したらOK POST /hello HTTP/1.1 x-amz-date: 20220222T135041Z x-amz-content-sha256: xxxxxxxxxxxxxxxxxxxxxxxxxxxxx authorization: AWS4-HMAC-SHA256 Credential=xxx, SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date, Signature=xxx Content-Length: xx Content-Type: application/json Host: xxxxxxx.execute-api.ap-northeast-1.amazonaws.com [body] GET の場合だとこう。 axios GET時の仕様がややこい。 async function main() { const headers: Record<string, string> = { host: hostname, }; const path = "/hello"; const req = await signRequest(headers, "GET", path); const resp = await axios.get(`https://${hostname}${path}`, { headers: req.headers, }); console.dir(resp.data); } まとめ 一度作ってしまえば楽ちんですね ? AWS SDK for JavaScript v3 のリファレンス置いておきます。