20200520のNode.jsに関する記事は7件です。

Vue.jsでAPIを返してみる

概要

Vue.jsで作成してVercel(旧now)とfreenomで独自ドメインのサイトを作るというものです。

できたもの


2020/05/23
動いたのでソースコード修正しました!!!

環境

macOS Catalina 
Visual Studio Code 1.45.1
Node.js: 12.8.1

構成

api(vercelはapiフォルダにメインで使うJSをいれるらしい)
 - server.js
public(staticファイルはpublicフォルダの中へ)
 - index.html
 - style.css
package.json
vercel.json
package-lock.json
node_modules

こんなイメージ

コード

<html>
  <head>
    <title>Hello My WebSite!</title>
    <link rel="stylesheet" media="all" href="style.css">
  </head>
  <body>
    <div id="app" class="waku">
    <h1>3yakaさん家のにゃんこはなにしてる?</h1>
    <p>猫カメラが撮った写真の最新10件がランダムに表示されるよ</p>
    <button id="testbutton" v-on:click="getData()"></button>


       <p class="mes"> {{ message }}
        <img class="imgsiz" v-bind:src="src" /><!-- データバインディングの場合はカッコをくくらなくて呼び出せます -->
      </p>
      </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                message: 'Hello Vue!',
                src:'https://i.gyazo.com/9360a06096a20ab93a79a4793f7670dd.jpg' // 画像のメッセージ初期値
            },

            methods: {
               getData: async function(){
                  let e;
                    const response = await axios.get('/api')
                    for (var i = 0 ; i < 9 ; i++){
                        e = Math.floor(Math.random () * 10);
                    }
                    const date = new Date(response.data[e].created_at);
                    let datef = date.toLocaleString();

                    this.message = `この写真を撮った時間は${datef}だよ`;
                    this.src = response.data[e].url; // 取得した画像差し替え
                    console.log(response.data[e].url);
                },
            },

            mounted: function(){
                this.getData();
            }
        })
    </script>
  </body>
</html>
node.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

const Gyazo = require('gyazo-api');
const gyazoclient = new Gyazo('***');
// axiosライブラリを呼び出す
const axios = require('axios');

// ローカルでもサーバー上でもみれるようにする
app.use(express.static(__dirname + '/../public'));
app.get('/api', async function(req, res) {
  let response;
  try {
    response = await gyazoclient.list();

  } catch (error) {
    console.error(error);
  }
  //結果をJSONに割り当てる
  res.json(response.data);
});


(process.env.NOW_REGION) ? module.exports = app : app.listen(PORT);
console.log(`Server running at ${PORT}`);

上記でローカルのサーバーだと動くけどVercellにDeployするとapiが読めないと返ってくる。
vercelから出るIntended Nameserversの番号が4日くらい前にProtoOut Studioでやったときとだいぶ違う感じになってた。

a.zeit-world.co.uk
c.zeit-world.org
e.zeit-world.com
f.zeit-world.net

これが、こんな感じに

ns1.vercel-dns.com
ns2.vercel-dns.com

vercel特有のフォルダ構成とかに悩まされた

参考サイト

爆速!Vercelとfreenomで独自ドメインのサイトを無料で作成する - Qiita

感想

久々にコンソールログが真っ赤なサイトを見た。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[discord.js] グローバルチャットを作る!

概要

discord.jsのバージョンがv12になり、グローバルチャットの機構を作ったので、メモしとこうかなと思います。
(ほかの人も記事を書いているので参考程度に...)
解説とありますが、まだまだすごいdiscord.jsが扱えるわけではないので、間違っているかもしれないので
気軽に見てってください。

BOTの導入やNode.js等のインストールはすでに済んでいるものとします。

環境

名前 versionとか
Node.js v12.16.3
discord.js v12.2.0

OS : Windows10

コード

index.js
const discord = require('discord.js');// --1
const client = new discord.Client();

client.on("message", async message => { // --2

    if (message.channel.name === "チャンネル名"){ // --3

        if (message.author.bot) return; // --4

        if (message.attachments.size <= 0) {
            message.delete()
        }

        client.channels.cache.forEach(channel => { // --5
            if (message.attachments.size <= 0){ 
                const embed = new discord.MessageEmbed() // --6
                    .setAuthor(message.author.tag, message.author.avatarURL())
                    .setDescription(message.content)
                    .setColor(0x2C2F33)
                if (channel.name === "チャンネル名"){ // --7
                    channel.send(embed)
                    return
                }
                return
            }
            if (!message.attachments.forEach(attachment =>{ //--8
                const embed = new discord.MessageEmbed()
                    .setAuthor(message.author.tag, message.author.avatarURL())
                    .setImage(attachment.url)
                    .setDescription(attachment.url)
                    .setColor(0x2C2F33)
                if (channel.name === "チャンネル名"){
                    channel.send(embed)
                    return
                }
                return
            }));
            return
        });
    }
});

client.login("BOT_TOKEN") // --9

解説

--1 : discord.jsの読み込みをしています
--2 : Message イベントが起こったときに、この括弧内の処理がされます
--3 : Message が送信されたチャンネルが チャンネル名だった場合になるように条件分岐をしています
--4 : 再帰を防ぐために、Messageを送信した人がBOTだった場合は処理を行わないように return を書いています

--5 : BOTが参加しているすべてのサーバーのチャンネルを取得しています(簡単に言うとforEachはforに似たようなもの)
--6 : v11.xでは discord.RichEmbed()でしたが、v12.xでは discord.MessageEmbed() になりました
--7 : 取得したチャンネルのうちチャンネルの名前が チャンネル名 か否かを判断する条件分岐です
--8 : 写真等が送信されたときに処理が行われます (解説が書けそうでしたら後日書きます)
--9 : これがなければBOTにはログインしてくれません

まとめ

グローバルチャットはBOTが導入されているサーバーで会話することができます。
ただトラブル等起きやすいので、グローバルチャットを利用するときは常識を守りましょう。

上のコードはシンプルな方です。まだまだいろんな機能を付け加えられると思うのでぜひチャレンジしてみてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Box UI ElementsのContent OpenWithでファイルの更新に反応してみた クライアントサイド編

この記事のシリーズ:

Box UI ElementsのContent OpenWithでBox Editをつかってみた
Box UI ElementsのContent OpenWithでG Suiteを開いてみた
Box UI ElementsのContent OpenWithでファイルの更新に反応してみた
Box UI ElementsのContent OpenWithでファイルの更新に反応してみた クライアントサイド編 ← この記事

コードは、Githubでも確認いただけます。

前回の内容と今回試すこと

前回、Box UI ElementsのContent OpenWithでファイルの更新に反応してみた という記事の中で、Box UI ElementsのOpenWithを使って、カスタム画面から変更した際に、サーバーサイドでロングポーリングをおこない、変更を検知して再描画するという内容を書きました。

@daichiiiiiii さんから、再度コメント以下のようなコメントいただきました。ありがとうございます!

BoxのWebアプリの場合、ClientサイドでLong PollingとEvent logチェックしています。
同じAppUserを複数ユーザが使う場合には、データ流出等につながる可能性があるので適しませんが、1:1(AppUser:実User)の場合であればクライアントサイドに実装しても良い気もします。

確かにアクセストークンが漏れても構わない場合、クライアントサイドで行ったほうが効率が良さそうです。
とはいえ、OpenWithやPreviewを表示するためにすでにダウンスコープしたアクセストークンを露出させています。
同じアクセストークンでEventを購読できるなら問題なさそうです。→ 同じトークンでEventを取得できます。
というわけで、早速こちらもためしてみました。

変えたところ

変更の検知をクライアントサイドに寄せるので、再びサーバー側のロジックはシンプルなものになります。

app_client_long_polling.js
const express = require("express");
const boxSDK = require("box-node-sdk");
const config = require("./config.js");

const app = express();

app.set("views", ".");
app.set("view engine", "ejs");

/**
 * setup.jsで作成したファイルとユーザー
 */

const USER_ID = "12771965844";
const FILE_ID = "665319803554";

app.get("/", async (req, res) => {
  try {
    const sdk = boxSDK.getPreconfiguredInstance(config);
    // AppUserの権限でClientオブジェクトを作成
    const auClient = sdk.getAppAuthClient("user", USER_ID);

    // トークンをダウンスコープする
    // APIリファレンスには載っていないが、UI Elementsの説明には書いてあるAPI
    // ここでは、OpenWithで必要なものと、Previewで必要なものを両方スコープにいれてトークンをダウンスコープする
    const downToken = await auClient.exchangeToken(
      [
        "item_execute_integration",
        "item_readwrite",
        "item_preview",
        "root_readwrite",
      ],
      `https://api.box.com/2.0/folders/0`
    );

    // テンプレートにパラメータを渡して、HTMLを返す
    res.render("index_client_long_polling", {
      fileId: FILE_ID,
      token: downToken.accessToken,
    });
  } catch (e) {
    console.error(e.toString());
  }
});

const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`express started on port ${port}`);
});

次に、クライアントコードでイベントを購読するようにします。

index_client_long_polling.ejs
<!DOCTYPE html>
<html lang="en-US">
<head>
    <meta charset="utf-8" />
    <title>Sample</title>

    <link href="https://cdn01.boxcdn.net/platform/elements/11.0.2/ja-JP/openwith.css" rel="stylesheet" type="text/css"></link>
    <link href="https://cdn01.boxcdn.net/platform/preview/2.34.0/ja-JP/preview.css" rel="stylesheet" type="text/css"></link>

    <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=es6,Intl"></script>
    <script src="https://cdn01.boxcdn.net/polyfills/core-js/2.5.3/core.min.js"></script>

    <script src="https://cdn01.boxcdn.net/platform/elements/11.0.2/ja-JP/openwith.js"></script>
    <script src="https://cdn01.boxcdn.net/platform/preview/2.34.0/ja-JP/preview.js"></script>

    <style>
        .openwith-container {
            margin-left: 250px;
        }
        .preview-container {
            height: 800px;
            width: 100%;
        }
    </style>

</head>
<body>
<h3>File Id: <%= fileId %></h3>

<div id="container">
    <div class="openwith-container"></div>
    <div class="preview-container"></div>
</div>

<script>
    // app.jsから渡されたパラメータ
    const fileId = "<%= fileId %>"
    const token = "<%= token %>"

    const openWith = new Box.ContentOpenWith();
    openWith.show(fileId, token,  { container: ".openwith-container"})

    let preview = new Box.Preview();
    preview.show(fileId, token, { container: ".preview-container", autoFocus: false });


    openWith.addListener("execute", async () => {
        // openWithが開かれたので、ロングポーリング開始

        // リアルタイムサーバーのURLを取得
        const optionsRes = await fetch("https://api.box.com/2.0/events", {
            method: "OPTIONS",
            headers: {
                "Content-Type": "application/json; charset=utf-8",
                "Authorization": `Bearer ${token}`,
            }
        });
        const optionsJRes = await optionsRes.json();

        let lastSequenceId = 0; // 最後のSequenceID
        const subscribe = async (streamPosition = "now") => {
            // リアルタイムサーバーに対してロングポーリングを行う
            // CORSエラーを避けるため、シンプルなリクエストを使う
            let rtsRes = await fetch(optionsJRes.entries[0].url, {
                method: "GET",
                mode: "no-cors",
                headers: {
                    "Content-Type": "application/x-www-form-urlencoded"
                }
            })

            // ロングポーリングからはtype: "opaque"というのが帰ってくるのだけど、、よくわからない・・・。
            // とはいえ、応答が帰ってくると、何かのイベントが発生したことはわかる。
            // console.log("rtServer res", rtsRes)
             
            // 何かのイベントが発生したので、EventAPIをGETで叩き、詳細情報を取り出す。
            // 上のロングポーリングだけに反応して再描画すると、再描画のプレビューイベントを拾ってしまい、
            // 無限ループしてしまうので、イベントを更新に絞って確認する必要がある。
            const qs = new URLSearchParams();
            qs.set("event_type", "ITEM_UPLOAD"); // 更新だけに絞る。
            qs.set("stream_type", "sync");
            qs.set("stream_position", streamPosition); // 初回は"now", 2回め以降はnext_stream_positionが入る

            const getRes = await fetch(`https://api.box.com/2.0/events?${qs.toString()}`, {
                method: "GET",
                headers: {
                    "Content-Type": "application/json; charset=utf-8",
                    "Authorization": `Bearer ${token}`,
                }
            });
            const getJRes = await getRes.json();

            // 複数もどってくるイベントを、対象ファイルの更新のもので、最新のものに絞る
            const latestEvent = getJRes.entries.reduce((acc, cur) => {
                if(cur.event_type === "ITEM_UPLOAD"
                    && cur.source
                    && cur.source.type === "file"
                    && cur.source.id === fileId) {
                    if(!acc) {
                        return cur;
                    }
                    return (cur.source.sequence_id > acc.source.sequence_id) ? cur : acc;
                }
                return null;
            }, null)

            // 前回処理したイベントより進んでいるときだけリロードする
            // 初回にstreamPosition === "now"でイベントを取得すると、イベントが何も帰ってこない。
            // 対象のファイルの更新イベントじゃないかもしれないけどリフレッシュかける。ここはもうちょっとうまくやれそうな気もする。
            if(streamPosition === "now" || latestEvent && latestEvent.source.sequence_id > lastSequenceId) {

                // 今回処理するイベントのsequence_idを保存
                if(latestEvent) {
                    lastSequenceId = latestEvent.source.sequence_id;
                }

                // previewだけを描画し直す。
                preview = new Box.Preview();
                // 毎回プレビューの位置までスクロールされたくないのでautoFocus:false
                preview.show(fileId, token, { container: ".preview-container", autoFocus: false });
            }
            // 次のイベントへ
            await subscribe(getJRes.next_stream_position)
        }
        // イベントの購読開始
        await subscribe();
    })

</script>
</body>
</html>

まとめ

コードはもうすこし改善の余地はありそうですが、とりあえずクライアントサイドからポーリングし、画面を再描画することができました。

クライアントサイドだけだと、SDKが利用できないので、少しコードを書くのが大変です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

簡単に始める仮想通貨EOSプログラミング(eosjs v16.0.9)

今回プログラミング初心者でも、仮想通貨EOSプログラミングを始められるように、コピペで実装できるプログラム総集編を紹介したいと思います。

javascriptのnode.jsとscatterウォレット操作を行う基本的なプログラミングになります。

もしプログラミングを始める際にはテスト環境であるテストネットのアカウントを作っておきましょう。
仮想通貨EOSをわざわざ購入せずにテスト版のEOSでプログラミングをすることが可能です。

仮想通貨EOSテストネットの利用方法

node.js動作プログラミング

node.jsによるEOSの操作についてのまとめです、トリガー処理とボット処理を組み合わせれば色んな使い方ができると思います。

EOSプログラミングの始め方
送金プログラミング
ステーキングプログラミング
アンステーキングプログラミング
RAM購入プログラミング
RAM売却プログラミング
EOSアカウント作成プログラミング

Scatter動作プログラミング

デスクトップウォレットScatter(スキャッター)によるEOSの操作についてのまとめです。HTMLに組み込んでdApps作成などの用途に使用できます。

Scatterプログラミングの始め方
送金プログラミング
ステーキングプログラミング
アンステーキングプログラミング
RAM購入プログラミング
RAM売却プログラミング
EOSアカウント作成プログラミング

最後に

仮想通貨EOSの基本的には英語圏と中華圏しかドキュメントがないので、日本向けになにか役に立てばと思っています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Promiseでお手軽に並列処理

概要

ある処理を行うのに事前に複数の事前処理が必要な場合、並列してできると処理が速くなる場合がある。コールバック地獄にならない記述がnodeだとできる。

3つのタスクが全て完了したら本処理を実行する例。

var Task1 = new Promise(function(resolve, reject) {
  //事前処理1

  resolve();  //これで事前処理1が終わったことを宣言する
});

var Task2 = new Promise(function(resolve, reject) {
  //事前処理2

  resolve();  //これで事前処理2が終わったことを宣言する
});

var Task3 = new Promise(function(resolve, reject) {
  //事前処理3

  resolve();  //これで事前処理3が終わったことを宣言する
});

Promise.all([Task1, Task2, Task3]).then(function () {
  //本処理
});

スッキリ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HTTP APIでLambda関数にPOSTしたbodyの値が '[object Object]' になる時の解決法

AWS Lambdaの関数にHTTP APIで値をPOSTする際につまづいたのでメモ。

環境

  • macOS
  • AWS Cloud9
  • Node.js 12

問題

Node.jsのfetch()メソッドで、自作したLambda関数にPOSTで値を送信しました。
Lambdaの呼び出しはAPI GatewayのHTTP APIを用いています。

Lambda関数のコード

デバックするために、Lambda関数では一旦eventを返すようにしています。
console.logで出力してCloudWacth Logsで確認しても良いと思います。

exports.handler = async(event, context) => {
    console.log(event)
    ...
    return event
};

実行コード

require('dotenv').config()
const env = process.env

var fetch = require("node-fetch")

fetch(env.LAMBDA_URL, { // Lambda関数のAPI URL
        method: 'POST',
        headers: {
            'Content-type': 'application/json'
        },
        body: {
            "massage": "test massage"
        },
    })
    .then(function(res) {
        return res.json();
    })
    .then(function(event) {
        console.log(event);
    })

eventの中身を確認すると、

{
version: '2.0',
routeKey: 'XXXXXXXXXX',
...
body: '[object Object]',
isBase64Encoded: false 
}

bodyの値が'[object Object]'になっています。

解決方法

どうやら、Lambda関数に値を渡すときは、Payloadとして渡す必要があるようで、JSON形式のデータはJSONの文字列に変換した状態で送る必要があるようです。
Payloadに指定した値は文字列に変換されるので、オブジェクトをそのまま指定すると、[object Object]に変換されてしまいます。
※オブジェクトにtoString()メソッドを適用すると、返り値は[object Object]なる

POSTするbodyの値を、JSON.stringify()で文字列に変換して送ることで解決できます。

修正後のコード

fetch(env.LAMBDA_URL, {
        method: 'POST',
        headers: {
            'Content-type': 'application/json'
        },
        body: JSON.stringify({ // JSONを文字列に変換する
            "massage": "test massage"
        }),
    })
    .then(function(res) {
        return res.json();
    })
    .then(function(event) {
        console.log(event);
    })

出力を見てみると、

{
version: '2.0',
routeKey: 'XXXXXXXXXX',
...
body: '{"massage":"test massage"}',
isBase64Encoded: false 
}

POSTしたbodyの値をきちんと確認できました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsで「Could not find a JavaScript runtime」というエラーが出てrails s ができなくなった

rails s ができない

久しぶりにRailsを触り、rails sでサーバー起動させると
長文のエラーが発生し、一番下に「Could not find a JavaScript runtime」と書いてありました。

解決策

調べたところ解決策は2つあるみたいです。
gem 'therubyracer' をGem fileに追記し bundle installを実行する。
・Node.jsをhttps://nodejs.org/en/からインストールする。

GitHub ExecJS
https://github.com/sstephenson/execjs
スクリーンショット 2020-05-16 1.42.21.jpg

私はNode.jsをインストールし
「rails s」できるようになりました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む