npm パッケージを beta 版で publish する

開発中の npm パッケージを publish して動作確認する必要があったのでその時の手順メモ.

バージョン指定

npm パッケージの package.jsonversion を, 1.0.0-beta.0 のように末尾に-beta-? が付くように書き換える.

またはnpm version premajor --preid betaコマンド打つと上記と同じような感じに書き換えてくれる.

https://docs.npmjs.com/cli/v6/commands/npm-version

特にbetaという文字列でなければいけないということではないので, nextでもnightlyでも開発中バージョンとわかるものであれば良さそうだけど様々なリポジトリ見る限りはbetaが多そう?

publish

npm publish --tag beta のようにタグを指定して publish する.

ちなみに, タグを付けないと default でlatest タグが設定される.

https://docs.npmjs.com/cli/v6/commands/npm-publish

使うとき

npm install hogehoge@beta のようにタグ指定でインストールすればよい.

またはnpm install hogehoge@1.0.0-beta.0 のようにバージョン直指定でもよい.

在宅ワーク中に家族に会議中だとアピールするライフハック

現在在宅ワーク中なわけだけど, 家族にミーティング中ですとアピールするために, ミーティングはいい感じのランプを点灯させたりしている。

f:id:magaming:20211005194559j:plain
こんなやつ

ただミーティング始まる/終わったときに手動でON/OFFするの面倒なので, 自動化してみた。

事前準備

まずは遠隔で操作できるように, スマートプラグを用意。

TP-Link のやつが安いのでそれにした。IFTTT 対応しているなら別なやつでもよさそう。Amazon でポチったら速攻届いたので, Webhook の設定して HTTP request 投げて点いたり消えたりしたのを確認。

www.wassyoi-hack.com

Chrome 拡張を作る

弊社ではミーティングに Google Meets を使用しているので, meet.google.com ドメインを開いたときにランプを点灯, 閉じたら消灯, みたいにしたい。今回はそれ用の Chrome 拡張を作った。どうやら chrome.tabs API を使えば現在開いているタブ一覧とかを取得できるので, それを使ってみる。

developer.chrome.com

manifes.json はこんな感じ。

{
  "manifest_version": 2,
  "name": "Google Meets 開いたらランプつけるくん",
  "description": "Google Meets 開いたらランプつけてくれます",
  "version": "1.0",
  "background": {
    "scripts": ["event.js"],
    "persistent": false
  },
  "permissions": ["tabs", "*://maker.ifttt.com/*"]
}

permissions に ifttt のドメインを指定しているのは, こうしないと CORS エラーで怒られるため。あとは適当に js 本体を用意。

const googleMeetsDomain = "meet.google.com";
const turnOnUrl =
  "https://maker.ifttt.com/hogehogehogel";
const turnOffUrl =
  "https://maker.ifttt.com/fugafugafuga";

var isTurnOn = 0;

function watchTabs() {
  chrome.tabs.query({}, (tabs) => {
    const isOpeningMeets = tabs.some((tab) => {
      return tab.url.match(/meet.google.com/);
    });

    if (isOpeningMeets && !isTurnOn) {
      request(turnOnUrl);
      isTurnOn = 1;
    }

    if (!isOpeningMeets && isTurnOn) {
      request(turnOffUrl);
      isTurnOn = 0;
    }
  });
}

function request(url) {
  var req = new XMLHttpRequest();
  req.open("GET", url);
  req.send();
}

chrome.tabs.onUpdated.addListener(watchTabs);
chrome.tabs.onRemoved.addListener(watchTabs);

chrome.tabs.query で全タブの url をなめて, Meets の url が含まれていれば ON, 含まれていないなら OFF する感じ。onRemoved はタブを閉じたとき、onUpdated はタブが更新されたときのイベント。Chrome 自体を閉じたときはこれだとダメだけど, あんまり困ってないのでまぁよいか, となっている。

実際の様子

f:id:magaming:20211006201118g:plain

Meets だけじゃなくて, 例えば youtube でご機嫌な動画を流したときにミラーボールを点灯するとか, 仕事中に Twitter を見始めたら吹き矢が飛んでくる, みたいなことに応用が効きそう。

Firebase Authentication の匿名ユーザーを削除する

Firebase Authentication で匿名ユーザーを使う場合, プランに関わらずユーザーアカウントの上限は1億までになっている。

Firebase Authentication の上限


つまり1億PV以上になると新たに匿名ユーザーを作れず破滅する可能性があるので, 定期的にユーザーの削除が必要になってくる。


めちゃめちゃバズらないと1億PVはそうそう行かなそうだけど, 可能性はゼロではないので対策しておいたほうがよさそう。


匿名ユーザーの削除は Firebase 側では自動でやってくれたりはしないので、自動でスクリプトなどを用意する必要がある。


今回は golang で削除スクリプトを実装した。

package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "time"

    firebase "firebase.google.com/go/v4"
    "firebase.google.com/go/v4/auth"
    "google.golang.org/api/iterator"
)

const maxDeletableUsersCount = 1000

func main() {
    ctx := context.Background()

    app, err := firebase.NewApp(ctx, nil)
    if err != nil {
        log.Fatalf("error new firebase app: %s\n", err)
    }

    auth, err := app.Auth(ctx)
    if err != nil {
        log.Fatalf("error create auth client: %s\n", err)
    }

    users, err := getInactiveAnonymousUsers(ctx, auth)
    if err != nil {
        log.Fatalf("error get users: %s\n", err)
    }

    fmt.Printf("%v個の匿名ユーザーアカウントが削除されます。よろしいですか? [y/n]:", len(users))

    var answer string
    fmt.Scan(&answer)

    if answer != "y" && answer != "Y" {
        os.Exit(1)
    }

    successCount, failureCount, err := deleteUsers(ctx, auth, users)
    if err != nil {
        log.Fatalf("error delete users, error:%s, success:%v, failure:%v\n", err, successCount, failureCount)
    }

    fmt.Printf("delete user complete, success:%v, failure:%v\n", successCount, failureCount)
}

func getInactiveAnonymousUsers(ctx context.Context, auth *auth.Client) ([]string, error) {
    var users []string

    iter := auth.Users(ctx, "")
    for {
        user, err := iter.Next()
        if err == iterator.Done {
            break
        }

        if err != nil {
            return nil, err
        }

        // 匿名ユーザーではない場合削除しない
                 // 匿名ユーザーしかいない場合は問答無用で削除してしまってよさそう
        if len(user.ProviderUserInfo) > 0 {
            continue
        }

        // LastLogInTimeStamp は ミリ秒 までを含めた値が返ってくるのに対し
        // time.Unix()は 秒 までを含めた値が返ってくるので, 比較できるように丸める
        // https://firebase.google.com/docs/reference/admin/java/reference/com/google/firebase/auth/UserMetadata#public-long-getlastsignintimestamp
        lastLogInTimestamp := user.UserMetadata.LastLogInTimestamp / time.Second.Milliseconds()
        // 1ヶ月以上ログインしていないユーザーを削除対象にする
        if lastLogInTimestamp < time.Now().AddDate(0, -1, 0).Unix() {
            users = append(users, user.UID)
            fmt.Printf("deletable user, uid:%s, last login:%v \n", user.UID, time.Unix(lastLogInTimestamp, 0))
        }
    }

    return users, nil
}

func deleteUsers(ctx context.Context, auth *auth.Client, uIDs []string) (successCount int, failureCount int, err error) {
    successCount = 0
    failureCount = 0

    // auth.DeleteUsers で削除できるユーザーは1000件までなので, スライスを1000件に分割して繰り返し実行する
    for len(uIDs) > maxDeletableUsersCount {
        slice := uIDs[0:maxDeletableUsersCount]
        uIDs = uIDs[maxDeletableUsersCount:]
        result, err := auth.DeleteUsers(ctx, slice)
        if err != nil {
            return successCount, failureCount, err
        }
        successCount += result.SuccessCount
        failureCount += result.FailureCount
    }

    result, err := auth.DeleteUsers(ctx, uIDs)
    if err != nil {
        return successCount, failureCount, err
    }
    successCount += result.SuccessCount
    failureCount += result.FailureCount

    return successCount, failureCount, nil
}

あとはこれを cron 等で定期的に実行してあげれば安心。

【Golang】VSCodeで自動生成されるテストコードをカスタマイズする

こんにちは。Goのテスト、書いていますか?
私はめちゃめちゃ書いています。

VSCode拡張機能(vscode-go)を入れることで、テストコードを自動生成することができることを最近知ったのですが、これがとても便利です。

使い方は以下の記事でまとめられています。

kdnakt.hatenablog.com

これはめちゃめちゃ便利なのですが、デフォルトだと少し物足ないところがあったので、カスタムテンプレートを作成しました。

GitHub - Magaming/gotests-templates

vscode-goは内部でgotestsを使っているので、それ用のテンプレートを用意している形です。
具体的には次のようなことをしています。

  • go-cmpを使う
    • 標準だと reflect.DeepEqualで比較しているが、これだとtime.Time型を含んだ構造体の比較とかで死ぬ
      • go-cmpは、そこだけ無視して比較ができるのでめちゃめちゃ便利
  • %v --> %#v にする
    • フィールド名が出たほうがデバッグに役立ちそうなのでそうしている
  • あとはメッセージの文言少し変えているけどまぁこれは好みの問題

テンプレートをローカルに置いて、VSCodeのsetting.jsonに設定を加えれば反映されてあとはハッピーです。

"go.generateTestsFlags": [
  "-template_dir",
  "/your_local_directory/gotests-templates/templates"
]

皆様もテンプレート作って幸せな生活をしてはいかがでしょうか。

カウントダウンタイマーアプリを作った

1月末で現職を退職予定なんだけど、「いつまで出社なの?」とよく聞かれるので、退職までの残り時間をシェアできるアプリを作った。
timer-maker.com

スキルセット

フロントエンド:React.js + TypeScript
ホスティング:Netlify

最初firebaseを使おうと思ったけど、入力項目がタイトルと日付の2項目だけなのでURLパラメータに保存することにした。
それならフロント側だけ考えれば済むので。

Netlify所感

静的サイトホスティングにはNetlifyいいよ!巷でウワサだったで使ってみた結果。

メリット
デメリット

ない

最後に

timer-maker.com

2020.1.31 追記

OGP画像が生成できるようになりました!

2019年没アプリ達の供養

2019年ももうすぐ終わり。今年は個人でサービス開発するぞ!と意気込んでたけど、結局リリースに至ったものは1つもなし…

せっかくなので没となったアプリ達の供養を、ここに執り行う。
 

f:id:magaming:20191227132844p:plain:w200

続きを読む