Node.jsで持続的 Cookie に対応する


発端

REST 使うコンソールアプリを TypeScript で書きたかったので、 Node.js と axios を使ってコードを書いていたのですが、ログイン操作や読み取り操作など毎回プロセスを起動して落とす設計にしたところ、 Permanent Cookie がプロセス終了とともに消滅してしまう状態でした。

Permanent Cookie?

mdn によると セッション Cookie持続的 Cookie の二つがあると紹介しており、前者は有効期間の指定がないのでクライアントを終了すると消えるとあります(ECのカートのような一時的なセッションで使われる感じですかね)。そして後者は設定された有効期間までは存続すると読み取れそうです(ログイン用途で使えそう)。
実際試したところ、ブラウザや iOS/macOSのAPI はプロセス再起動しても自動でCookieを付与してくれたのですが、Node.js + axios ではやってくれませんでした。どうやらディスクに保存するような別の仕組みが必要そうです。

対応方法

最悪デーモンプロセスが必要かもな、、と思いつつ調べたところ tough-cookie という強そうな名前のライブラリを見つけることができました。これ自体はディスクに保存しないのですが、ドキュメントを読んでみると Store API という仕組みがあることに気がつきます。

The default is MemoryCookieStore which can be found in the lib/memstore.js file.

とても気になる文言です。 lib/ に FileCookieStore のような実装があればそれに切り替えるだけで実現できそうですが残念ながらありませんでした。

追加のライブラリを検索

もしかしたら誰かが実現しているかもと思い、次にGoogleで "tough-cookie file" と検索しようとしたところ、 tough-cookie-filestore がサジェストされました。おお、これは試してみたい。

コード

まず axios と tough-cookie をどう連携させるかという問題に直面するわけですが、以前 axios が同一プロセス内でも cookie を付与してリクエストを送ってくれないという別の問題に対処すべく こちらの記事 を読んでいたので、 cookiejar を使えばうまくいくことを運良く知っていました。

なので axios cookiejar で検索してみたわけですが今度は同じゆめみの @Ancient_Scapes さんの記事に使い方が書いてあったので時間短縮できました(助かる〜): axiosでrequestと同じようにjarを使えるようにする方法

というわけで filestore のインスタンス作ってセットするだけでした。簡単。

import axios from 'axios';
import axiosCookiejarSupport from 'axios-cookiejar-support';
import { CookieJar } from 'tough-cookie';
import FileCookieStore from 'tough-cookie-filestore';

export const createClient = (baseURL: string, cookiePath: string) => {
  const filestore = new FileCookieStore(cookiePath);
  const jar = new CookieJar(filestore);
  axiosCookiejarSupport(axios);
  return axios.create({
    withCredentials: true,
    jar,
    baseURL
  });
};

TypeScriptでもバッチリでした。素晴らし。

注意事項としては、上記のコードではデフォルトで cookiePath に空のファイルがないとエラーになります。writeFileしておきましょう。

余談

cookiejar ってどういう意味なんだろと思いググってみたところ、 weblioのページ が引っ掛かったのですが、

クッキーが保存される瓶(時々お金が隠される)

という意味らしいです。時々お金が隠されるというところがとても気になります、海外のアニメ作品とかかな