React で作った Web アプリケーションを WOFF にデプロイしてみた

## はじめに
先日、[LWTT名古屋](https://lwugdev.connpass.com/event/334271/)を開催し、WOFFのアプリケーション開発の流れについてフォーカスし、LTを実施しました。
オフラインイベントだけの公開だと勿体無い気がしたので、Reactで作ったWebアプリケーションを、WOFFとして適用するまでの超絶簡単な流れなどを書いていければと思います！

本実装内容は、Githubリポジトリにも用意しましたので、こちらも参考になればと思います！
🔻woff-mini-app-demo
https://github.com/TaroYamada1997/woff-mini-app-demo

🔻WOFFとは
https://developers.worksmobile.com/jp/docs/woff-guide

## Reactで作ったWebアプリケーションの実装
ソースコードは以下のようなサンプルを実装してみました。

```react
import React, { useState, useEffect } from 'react';
import { Button, TextField, Container, Grid, Typography } from '@mui/material';

const WorksmobileSDK = () => {
  const [userName, setUserName] = useState('');
  const [formData, setFormData] = useState({
    applicant: userName || '',
    department: '',
    amount: '',
    reason: '',
  });
  const [isLoaded, setIsLoaded] = useState(false);

  useEffect(() => {
    const script = document.createElement('script');
    script.src = 'https://static.worksmobile.net/static/wm/woff/edge/3.6/sdk.js';
    script.async = true;
    script.onload = () => {
      const woffId = process.env.REACT_APP_WOFF_ID;

      // WOFF 初期化
      window.woff.init({ woffId })
        .then(() => {
          console.log('WOFF initialized successfully!');
          getProfile();
          setIsLoaded(true);
        })
        .catch((err) => {
          window.alert(err);
          console.error(err);
        });
    };
    script.onerror = () => {
      console.error('Failed to load SDK');
    };
    document.body.appendChild(script);

    return () => {
      document.body.removeChild(script);
    };
  }, []);

  const getProfile = () => {
    window.woff.getProfile().then((profile) => {
      setUserName(profile.displayName);
    }).catch((err) => {
      console.log(err);
      window.alert(err);
    });
  }

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setFormData((prev) => ({
      ...prev,
      [name]: value,
    }));
  };

  const handleSubmit = async () => {
    const { applicant, department, amount, reason } = formData;

    // 稟議書メッセージ
    const message = `
      稟議書提出
      申請者: ${applicant}
      部署: ${department}
      金額: ${amount}
      理由: ${reason}
    `;

    if (isLoaded) {
      // WOFF SDKを通じてメッセージ送信
      window.woff.sendMessage({ content: message })
        .then(() => {
          console.log('稟議書が送信されました');
          window.alert('稟議書が正常に送信されました');
        })
        .catch((err) => {
          console.log(err);
          window.alert('送信エラーが発生しました');
        });
    } else {
      window.alert('WOFF SDK がまだ読み込まれていません');
    }
  };

  return (
    <Container>
      <Typography variant="h4" gutterBottom>
        稟議書フォーム
      </Typography>
      <Grid container spacing={2}>
        <Grid item xs={12} sm={6}>
          <TextField
            label="申請者名"
            variant="outlined"
            fullWidth
            name="applicant"
            value={formData.applicant}
            onChange={handleInputChange}
          />
        </Grid>
        <Grid item xs={12} sm={6}>
          <TextField
            label="部署名"
            variant="outlined"
            fullWidth
            name="department"
            value={formData.department}
            onChange={handleInputChange}
          />
        </Grid>
        <Grid item xs={12}>
          <TextField
            label="金額"
            variant="outlined"
            fullWidth
            name="amount"
            value={formData.amount}
            onChange={handleInputChange}
          />
        </Grid>
        <Grid item xs={12}>
          <TextField
            label="理由"
            variant="outlined"
            fullWidth
            name="reason"
            value={formData.reason}
            onChange={handleInputChange}
            multiline
            rows={4}
          />
        </Grid>
        <Grid item xs={12}>
          <Button
            variant="contained"
            color="primary"
            onClick={handleSubmit}
            fullWidth
          >
            送信
          </Button>
        </Grid>
      </Grid>
    </Container>
  );
};

export default WorksmobileSDK;
```

処理そのものは割愛しますが、 1点だけ工夫した？ところがあります。

```react
const script = document.createElement('script');
script.src = 'https://static.worksmobile.net/static/wm/woff/edge/3.6/sdk.js';
```

ここは、WOFFの初期化を行う必要があったため、少々強引ではありますがDOM操作を無理やり行なっています。Reactっぽくない書き方ですが。。

このアプリケーションを、たちまちはローカルで起動→[ngrok](https://ngrok.com/)で外からアクセス可能なURLを生成→WOFFに乗せるという感じでやってみました。
今回はローカルで起動する方法を取っていますが、外からアクセスできるURLであれば WOFF に乗せることは可能ですので、この辺りはお好みです。

```
$ npm i
$ npm run start
$ ngrok http http://localhost:3000
```

### WOFF アプリに登録する

ここからは、基本的にドキュメントに沿ってWOFFアプリを登録していきます。
ドキュメント以外では、山﨑さんの記事がわかりやすくまとまっていますので、参考になればと思います。

🔻山﨑さんの記事
https://qiita.com/mmclsntr/items/ab3987938da6ecd8d9b1

だいぶ端折りますが、ngrokで発行したURLを WOFF アプリケーションに適用します。

![スクリーンショット 2024-12-14 14.05.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952868/a7892caf-19de-b0ce-a333-ca66405254d2.png)


別途、`.env`ファイルを用意しましたので、そこに WOFF ID をコピーして追加しました。

## 動作させる

トーク画面上から、WOFFで作成したアプリケーションにアクセスすると、
以下のような申請画面が出るようになっています！（モバイル端末で操作しています。）

![IMG_2912.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952868/1149eb7d-6eca-1f42-9828-825b5777356a.png)

これを「送信」とすると、、

![IMG_3064.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952868/722c9e3c-dcb9-dbc2-66f7-88bceaad8a06.jpeg)


このようにトーク画面上に入力し、送信した内容がトーク画面に表示されます。

## おわりに

WOFF を使うと、LINE WORKS のトーク画面上で、外部のサービスを呼び出して利用するなど様々な活用シーンが生まれます。

個人的にも使っていてとても楽しいので、どんどん WOFF を使ったアプリケーションを紹介できたら良いなと思います！

今回は超簡単でしたが、終わります！では！