クライアントのpako.inflateがエラーになっちゃう対応

## TL;DR
サーバでpako.deflateして送信したデータが、クライアントでpako.inflateしようとすると「incorrect header check」エラー、または「undefined」となる問題の対策

:::note info
■サーバ側
サーバでpako.deflateは "raw:true" しろ
pako.deflate(JSON.stringify(data), { encoding: "utf-8", raw: true });

■クライアント側
base64をatobで変換した文字列をカンマ区切りでUint8Arrayを作成してpako.inflateに渡せ
inflateオプションは{ to: "string", raw: true }としろ
:::

## 背景
サーバでNode.jsを使いSSE方式で所定のクライアント群にデータを送信する
送信するデータはJSONでpako.deflateで圧縮し、BASE64でクライアントに送信する

Server JSON → pako.deflate → BASE64
↓
Client BASE64 → :thinking:pako.inflate → JSON.Parse

クライアントではBASE64をエンコードし、Pako.inflateで展開し元のJSON文字列を取得したい。
しかしpako.inflate展開時にエラーでハマった

#### 対策済みコード例
* サーバ側「Worker」が動作しデータを取得
取得した値をdeflateで圧縮
1秒周期でクライアントに送信

```Server.js
const pako = require("pako");
const express = require('express');
const app = express();

const { Worker } = require("worker_threads");
// ワーカースレッドを作成 - データ取得処理
const worker = new Worker("./worker.js");

// 内部バッファの初期化
let internalBuffer = "";
// 接続しているクライアントのリスト
let clients = [];

// ワーカースレッドからのデータを受信
worker.on('message', (data) => {
	// 受信したデータを変数に格納
	internalBuffer = pako.deflate(JSON.stringify(data), { encoding: "utf-8", raw: true });
});

// 初回のリクエストを受信するエンドポイント
app.get('/api/request', (req, res) => {
  res.header("Access-Control-Allow-Origin", "http://localhost:3000");
	// SSEの処理
	res.setHeader("Content-Type", "text/event-stream");
	res.setHeader("Cache-Control", "no-cache");
	res.setHeader("Connection", "keep-alive");
	res.setHeader("X-Accel-Buffering", "no");

	// クライアントをリストに追加
	clients.push(res);
	req.on("close", () => {
     	// 接続が閉じられた場合、クライアントをリストから削除
		clients = clients.filter((client) => client !== res);
	});
});

// 毎秒すべてのクライアントにデータをプッシュ
setInterval(() => {
	clients.forEach(client => {
		client.write(`data:application/octet-stream;base64,${btoa(internalBuffer)}\n\n`);
	});
}, 1000);

// サーバを起動
const PORT = process.env.PORT || 4000; 
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});
```

* クライアント側 React
取得した値をpako.inflateで展開、JSONを取得

```App.tsx
import React, { useEffect, useState } from "react";
import "./App.css";
import axios from "axios";
import pako from "pako";

const MainComponent: React.FC = () => {
  const groupServerAddress = "http://localhost:4000/api/request"
  useEffect(() => {
      const eventSource = new EventSource(groupServerAddress);
      // SSEからデータを受け取る処理
      eventSource.onmessage = async (event: MessageEvent) => {
        let eventData = event.data;
        // データが圧縮されているかどうかをチェック
        if (eventData.trim().startsWith("application/octet-stream;base64,")) {
          // Base64エンコードされたデータを取得
          eventData = eventData.substring("application/octet-stream;base64,".length);
          try {
				// Base64デコード
				const decodedData = atob(eventData.trim());
                const sourceArray = new Uint8Array(decodedData.split(",").map(Number)); 
               
//inflateで展開 - ここでエラーが出ていた
                const decompressedData = pako.inflate(sourceArray, { to: "string", raw: true });

				console.log("受信したデータ:", JSON.parse(decompressedData));
            } 
            catch (error) {
                console.log(error); 
            }
        } 
      };
      return () => {
        eventSource.close();
      };
    }
  }, [groupServerAddress]);
  return null;
};

function App() {
	return (
		<div className="App">
			<MainComponent /> 
		</div>
	);
}
export default App;
```

## 解説
・クライアント React側にてサーバからmessageを受信後
const decodedData = atob(eventData.trim()); 
でBASE64を変換し、そこからUint8Arrayを作っているが、これは文字列に対する操作になっており

たとえばatob後のデータが以下のケース場合
例）"237,146,77,78,195,78…"
eventData[0] = "2"
eventData[1] = "3"
eventData[2] = "7"
eventData[3] = ","
…
を元にしたByteArrayが生成される。
これをinflateすると「Incorrect header check」となる

```bad_code.js
//NGコード例
const decodedData = atob(eventData.trim());
const byteNumbers = new Array(decodedData.length);
for (let i = 0; i < decodedData.length; i++) {
 	byteNumbers[i] = decodedData.charCodeAt(i);
 }
// バイト値の配列をUint8Arrayに変換
const byteArray = new Uint8Array(byteNumbers);

//Incorrect header checkとなる
const decompressedData = pako.inflate(sourceArray, { to: "string", raw: true });
```

元データの通り
eventData[0] = "237"
eventData[1] = "146"
eventData[2] = "77"
…
としたUint8Arrayをinflateすれば解決する。

### inflateのエラー
・エラー「Incorrect header check」は、データの圧縮形式に問題があることを示す
・エラーは出ないが、結果が「undefined」は渡されるデータが誤っている