本アプリは、学校教育法第34条第2項に定める「学習者用デジタル教科書」ではありません。教科書発行者とは関係のない、個人が作成した教材ビューアです。 「デジタル教科書」は、検定教科書と結びついた制度上の呼び名です。本アプリは手元のPDF(自作プリントなど)に書き込むためだけの道具なので、制度上のデジタル教科書としては使えません。 リポジトリ名(
Digital_textbook)と公開URL(digital-textbook.giga-school.com)は経緯からそのままですが、アプリの表示名は「教材プリントメーカー」です。
学校現場の先生のために開発された、完全無料・サーバー(データベース)不要の教材プリント作成・共有Webアプリです。
取り込めるのは、自分で作った資料や、権利処理が済んでいる資料です。教科書・ドリル・ワークブックなど市販の教材を丸ごと取り込んで配ることは、著作権法第35条の範囲を超えます。
- 授業で使うためであっても、第35条(学校その他の教育機関における複製等)には「著作権者の利益を不当に害することとなる場合は、この限りでない」という条件があります。1冊まるごとの取り込み・配布は、これに当たります。
- 本アプリのP2P共有は「先生の端末から児童生徒の端末へ配る」機能です。配ってよい資料かどうかの判断は、使う先生の責任になります。
- この注意は、アプリを初めて開いたときにも画面に出ます(「わかりました」を押すと次回から出ません)。
PDFファイルをブラウザに読み込ませるだけで、ペンやマーカーによる書き込み、ふせんの貼り付け、豊富な「教育用スタンプ」の配置が可能になります。さらに、WebRTC(P2P通信)技術を採用しており、クラウドサーバーを経由せずに先生の端末から児童生徒の端末へ直接データを共有(配布)できます。
公開先: https://digital-textbook.giga-school.com/ 先生向けの使い方: docs/manual/manual.md(画面写真つき・全18章。公開ページ giga-school.com/apps/digital-textbook/manual/ の元になります)/MANUAL.md(これまでの版)
| 画面 | できること |
|---|---|
| 一覧(ホーム) | PDFの取り込み・教材を開く/消す、バックアップの書き出し/取り込み、Googleドライブ同期、アプリのインストール |
| 教材(編集) | えんぴつ・マーカー・けしごむ・もじ・ふせん・図形・スタンプ・リンク/音声・QR読み取り、ページ送り、拡大縮小、半ページ表示、全画面、提示モード、印刷、タイマー、P2P共有 |
書き込みは押した瞬間から自動保存されます(操作が止まって 0.8 秒後にブラウザの IndexedDB へ書き込み。タブを閉じるときにも取りこぼさず保存します)。保存ボタンはありません。
-
ツールバーは呼び出し式: 教材を開くと画面いっぱいがページになります。左上の 「ツール」 ボタンを押すと上からツールバーが下りてきて、「隠す」 で再びしまえます。ページを広く使うための作りです。
-
開いた直後は「選択」モード: いきなり線が引かれてしまう事故を防ぐため、教材を開いた直後は書き込みではなく選択モードから始まります。
-
基本ツール: 選択、えんぴつ(太さ4px)、マーカー(太さ24px・不透明度40%)、けしごむ、もじ、ふせん、リンク・音声の埋め込み、QRコード読み取り
- 色は 黒・赤・青・緑・オレンジ の5色から選びます。
- **けしごむは「押した書き込みを1つまるごと消す」**方式です(線の途中だけを削るのではなく、1本の線・1つのスタンプが単位)。
- リンク/音声は URL を入力すると丸いボタンがページに置かれ、ダブルクリックで Web ページが開く・音が鳴ります。
- ふせんは4色(黄・ピンク・水色・黄緑)。置いた直後からそのまま文字を打てます。
-
図形描画: 四角、円、直線、矢印(ドラッグした大きさで描かれます)
-
プリセットスタンプ 167 種: 押すとページの中央(半ページ表示中は見えている側の中央)に置かれ、そのまま移動・拡大縮小・回転ができます。
タブ 数 内容 評価 16 💮はなまる、たいへんよくできました/よくできました/がんばりました/みました/合格 の朱印スタンプ、100点、二重丸・まる・さんかく・チェック、👍いいね など 国語 41 縦書きテキスト(いつ・どこで・だれが・なにを・なぜ・どのように、はじめ/中/おわり、序論/本論/結論、主語・述語、筆者の考え など)、①〜⑳の丸数字、感情の絵文字 算数 42 1/10/100/1000 のブロック(1000は立体)、位取り表、筆算枠、2D図形(平行四辺形・ひし形・台形・五角形・六角形)、3D立体図形(立方体・直方体・円柱/いずれも中が透ける線画)、三角定規、+−×÷=<>、単位(L・dL・mL・kg・g・cm・m・km) 英語 10 Good/Nice、A〜D、🇺🇸🇬🇧、絵カード 理社 34 天気(晴れ・曇り・雨・雪)、地図記号(市役所・交番・消防署・田・畑・茶畑・神社・寺院 など)、実験器具(虫眼鏡・顕微鏡・温度計・磁石 など) 生活 24 発表・話し合い・ペア・グループ、給食・掃除・当番、時計、重要・はてな など マイ ― 自分で作ったスタンプ(下記) 「分度器」「三角定規」など一部は絵柄(文字・記号)としてのスタンプです。角度を測れる目盛り付きの道具ではありません。
-
マイスタンプ機能: 好きな文字(5文字まで)・色(カラーピッカーで自由に)・枠の形(枠なし/丸/四角)を組み合わせて自分だけのスタンプを作成できます。スタンプメニューの「マイ」タブ →「新しいスタンプを作る」から登録し、そのタブに並びます。保存先は端末の
localStorageで、バックアップにも含まれます。 -
元に戻す/やり直し: 直前の操作から30手前までさかのぼれます(ページを移動すると、そのページの履歴は作り直されます)。
-
半ページ表示: 「表示」ボタンから 「ページ全体を表示」/「左半分から読む」/「右半分から読む」 を選べます。縦向きのタブレットでも文字が十分な大きさで読めるよう、ページを中央で切って半分ずつ映します。ページ送りは「前半分 → 後半分 → 次のページの前半分」の順に進みます。**国語など右開きの教材は「右半分から読む」**を選んでください。切り取って見せているだけなので、書き込みの位置や保存データには一切影響しません。この設定は次回起動時にも覚えています。
-
提示モード(電子黒板向け): 画面右下のモニターのマークを押すと、文字とボタンがひとまわり大きくなり、教室のうしろの席からも読める大きさになります。
-
全画面表示: ヘッダー・ツールバーを隠してページだけを画面いっぱいに映します(
Fキーでも切り替え)。ブラウザのフルスクリーンに対応していない iPhone の Safari などでは、UIを隠す「集中モード」として動作します。 -
印刷: 画面右下のプリンターのマークで、いま開いている1ページを書き込みごと印刷します。横長のページは自動で「よこ向き」に切り替わります。
-
拡大・縮小: 50%〜300%(画面右下の
+−、キーボードの↑↓)。ページ全体が画面に収まる倍率は、端末の回転やウィンドウサイズの変化に合わせて自動で計算されます。 -
ページ移動: 画面下のバーの
←→、キーボードの矢印キー、ページ番号をタップして番号を直接入力、選択モード中は教材の上を左右にスワイプ。前回開いていた教材とページは記憶され、次回はその続きから開きます。
-
サーバー代ゼロ・情報漏洩リスクゼロ: Firebaseなどのクラウドデータベースを使わず、PeerJS (WebRTC) を使用して先生の端末と児童生徒の端末を直接つなぎます。
-
QRコード・URLで共有: 「共有する」ボタンを押すと、専用のQRコードとURL(
?host=…付き)が自動生成されます。児童生徒がこれを読み取り、合言葉を入れると、いま開いている1つの教材のPDFと書き込みデータがまるごとダウンロードされます。受け取った側では末尾に「(共有)」が付いた別の教材として保存されます。 -
合言葉(あいことば)が必要: 共有を始めると、先生の画面に5文字の合言葉が出ます。この合言葉はQRコードにもURLにも入っていません。口頭または板書で、教室の中だけに伝えてください。 URLだけを手に入れた人(校外へ転送された場合など)は受け取れません。合言葉は共有のたびに
crypto.getRandomValuesで作り直します。見まちがえやすい0/O1/I/Lは使いません。入力は小文字でも、空白やハイフンが混じっても通ります。1回の接続で3回まちがえると、その接続は切ります。 -
有効期限(既定30分): 共有には終わりの時刻があります。のこり時間は先生の画面に出ており、0になると自動で配布を終了します。10分/30分/60分から選べ、共有中に変えるとその時点から数え直します。もちろん「共有を終了して閉じる」でいつでも手動で終われます。
-
人数の上限(既定40人): 配れる人数の上限を 5/10/40/80人 から選べます。上限に達したあとの接続は断ります(受け取る側には「人数がいっぱいです」と出ます)。
-
受け取った人数が見える: 先生の画面に「受け取った人数 ◯ / ◯ 人」が出ます。気づかないうちに誰かが受け取っていないか、ここで確かめられます。
-
配ったあとは同期しません: 送られるのは「共有した時点のコピー」です。先生があとから書き足しても、児童生徒の端末には反映されません。
-
配布中は画面を開いたまま: 先生の端末がホスト(配信元)なので、共有モーダルを閉じると配布が止まります。1人が受け取ったあともそのまま待機し、続けて次の人に配れます。
-
ワンクリックでGoogleドライブ同期: ホーム画面の「Googleドライブに接続」を押すだけで、以降は「ドライブに保存」「ドライブから復元」のボタン操作だけで、各先生ご自身のGoogleドライブへ直接データを保存・復元できます。ファイルを手動でアップロード/ダウンロードする必要はありません。別の端末で同じGoogleアカウントに接続すれば、そのまま同期できます。
-
自動保存: 「自動保存」をオンにしておくと、書き込みや変更を少し待ってから自動でドライブへ保存します。
-
アプリの理念はそのまま: 同期にサーバー・データベースは使いません。保存先は「あなた自身のGoogleドライブ」にある
digital-textbook-backup.json1ファイルだけで、アプリが使うのはdrive.fileスコープ(このアプリが作成したファイルのみ)なので、他のドライブ内ファイルには一切アクセスしません。 -
かんたんJSONバックアップも継続利用可: 従来どおり「バックアップを書き出す/取り込む」でJSONファイル(
digital-textbook-backup-日時.json)として保存・復元することもできます。教材・書き込み・マイスタンプがすべて1ファイルに入ります。 -
追加 / 置き換えを選べる: 取り込み時に、中身(冊数・ページ数・マイスタンプ数・書き出した日時)を確認したうえで「追加で取り込む(既存データは残す)」と「置き換える(既存データを破棄)」を選べるので、複数端末の内容を統合したり、まっさらな状態にリセットしたりできます。同じ教材が重複したときは「(取込)」が付いた別の1件として追加されます。
💡 Googleドライブ同期の有効化について 同期パネルは OAuth クライアントID(
VITE_GOOGLE_CLIENT_ID)が設定されているときだけ表示されます。src/constants.jsには公式配布先(digital-textbook.giga-school.com)用の既定値が入っているため、上記の公開URLではそのまま使えます。 クライアントIDは、Google Cloud の「承認済みの JavaScript 生成元」に登録したオリジンからしか使えません。公開先のドメインを変えたら、Google Cloud 側の生成元も同時に書き換えてください(登録し忘れると、接続時に Google の画面で「エラー 400: origin_mismatch」が出ます)。 このリポジトリをフォークして別のドメインで公開する場合は、自分のクライアントIDを発行し、Google Cloud の「承認済みの JavaScript 生成元」に自分の公開先を登録してください(他人のIDのままでは接続が拒否されます)。手順は GOOGLE_DRIVE_SETUP.md を参照してください。
-
アプリ内のボタンからインストール: Chrome がインストール可能と判断すると、ホーム画面の右上に 「アプリを入れる」 ボタン(オレンジ)が出ます。押すだけでデスクトップ・ホーム画面に独立したアプリとして追加できます(アドレスバー右端のインストールアイコンからでも同じです)。
-
オフライン起動: Service Workerがアプリ本体と外部ライブラリ・フォントをキャッシュするため、一度読み込めばインターネットに接続できない環境でも起動して書き込みの続きができます(P2P共有・QRリンク先の閲覧・Googleドライブ同期はオンライン時のみ)。
-
更新は押してから: 新しいバージョンが公開されると「あたらしい バージョンが あります」の帯が出ます。「さいしんに する」を押した時点で切り替わります(書き込みの途中で勝手にリロードされないようにするためです)。
-
高精度QRコードリーダー: プリントに印字されたQRコードを、アプリ内から読み取ってその場でWebサイトを開けます(
Qキー、またはツールバーのQRアイコン)。QRコードをドラッグで囲むと、表示用に縮小される前の元のページ画像から、拡大版・白黒反転も含めて順に解析します。囲まずにクリックした場合はページ全体から探します。解析はBarcodeDetectorAPI があればそれを、無ければ jsQR を使います。https://http://で始まるURLだけを新しいタブで開き、それ以外は内容を表示するだけにとどめます。 -
タイマー&ストップウォッチ: 画面の左上に出せるフローティングパネル。タイマーは5分から始まり「+1分」「+5分」で足せます。ストップウォッチにも切り替えられ、授業の活動時間を測るのに最適です。
-
完全オフライン動作(ローカル保存): 読み込んだPDFや書き込みデータはブラウザの
IndexedDBに自動保存されます。一度開いた教材は、次回以降オフラインでもすぐに続きから開くことができます。 -
キーボードショートカット: ページめくり・ズーム・Undo/Redo・ツールの切り替えに対応(下表)。
-
スマホ・タブレットの「戻る」操作: 画面下のナビゲーションバーの「戻る」をタップするか、画面の左右どちらかの端から中央に向かってスワイプすると、開いているメニュー → 全画面表示 → 教材の画面 → 一覧 の順に1つずつ戻れます。誤ってアプリが終了したり、ブラウザで別のページへ移動してしまったりすることはありません。
-
端末をまたいでも書き込みがずれない: 書き込みは保存時のキャンバス幅とともに記録され、画面サイズの違う端末で開いたときに自動で位置と大きさを合わせ直します。
Esc 以外は教材を開いているときに効きます。入力欄や文字の編集中は、どのキーも書き込み側には効きません。
| キー | アクション |
|---|---|
→ / ← |
次のページ / 前のページ |
↑ / ↓ |
拡大(ズームイン) / 縮小(ズームアウト) |
Ctrl + Z |
元に戻す(Undo) |
Ctrl + Y または Ctrl + Shift + Z |
やり直し(Redo) |
V |
選択モード |
P / H / E |
えんぴつ / マーカー / けしごむ |
Q |
QRコード読み取りモード |
F |
全画面表示の切り替え |
Delete / BS |
選択したオブジェクトを削除(選択モード時) |
Esc |
1つ前の階層へ戻る(メニュー・モーダルを閉じる) |
? |
ショートカット一覧の表示/非表示 |
Mac では
Ctrlの代わりに⌘でも動きます。
| 操作 | 動作 |
|---|---|
| 画面下のナビゲーションバーの「戻る」をタップ | 1つ前の階層へ戻る |
| 画面の左右どちらかの端(28px以内)から中央へスワイプ | 1つ前の階層へ戻る |
| 選択モード中に教材の上を左へスワイプ | 次のページ |
| 選択モード中に教材の上を右へスワイプ | 前のページ |
戻る順番は「開いているモーダル → メニュー・パネル → ツールバー → 全画面表示 → 教材の画面 → 一覧」です。一覧(最初の画面)でさらに戻ろうとしても、アプリが終了したりブラウザで別のページへ移動したりすることはありません。
-
キャンバスへの書き込みを妨げないよう、画面の端から始まる指でのスワイプだけを「戻る」として扱います(Apple Pencilなどのペン入力・マウス操作はそのまま書き込みに使えます)。
-
縦方向に大きく動いた場合はスクロール操作として扱うため、一覧画面のスクロールも妨げません。
-
ページ送りのスワイプは選択モード中だけ働きます。えんぴつなどで書き込んでいる最中や、拡大してページが画面からはみ出しているとき(スクロールを優先)は働きません。
-
Frontend Framework: React 18
-
Build Tool: Vite 5
-
Styling: Tailwind CSS 3
-
Icons: Lucide React
-
Canvas Drawing: Fabric.js (v5)
-
PDF Rendering: PDF.js (pdfjs-dist v2)
-
P2P Networking: PeerJS
-
Local Storage: idb-keyval (IndexedDB) +
localStorage(マイスタンプ・表示設定・最後に開いたページなどの小さな設定) -
QR Code parsing: BarcodeDetector API / jsQR
-
QR Code generation: qrcode.js
-
Google Drive 連携: Google Identity Services + Drive REST API(SDKは使わず直接呼び出し)
-
PWA / Offline: vite-plugin-pwa (Workbox)
-
外部ライブラリの配信: 自己ホスト(
public/vendor/。scripts/sync-vendor.mjsが npm から生成)。バンドルには含めず、起動時に<script>として読み込んでグローバル(window.fabricなど)で使います。校内Wi-Fiでの取りこぼしに備えて最大3回まで再試行します。
index.html CSP・PWAメタ・インストール合図の受け取り
src/App.jsx アプリ本体(約2,700行。画面の組み立てとアプリの流れ)
src/main.jsx 起動と、更新のお知らせの帯
src/constants.js 保存キー・色・PDF解像度・ジェスチャーの判定値など
src/components/Chrome.jsx ヘッダー・フッター・タイマーパネル
src/data/stamps.js スタンプの分類と定義(167種)
src/data/sealStamps.jsx 朱印風スタンプの描画(はなまる・よくできました など)
src/hooks/useExternalScripts.js public/vendor/ のライブラリ読み込み(再試行つき)
src/lib/fabricHelpers.js fabric.js まわりの道具(保存形式・ツールモード・算数の図形)
src/index.css Part I 準拠の共通スタイル(safe-area / fluid type /
提示モード / 印刷 / reduced-motion)
public/vendor/ 自己ホストする外部ライブラリ(git 管理外・自動生成)
public/offline.html 圏外時の案内
public/pwa-install-hook.js beforeinstallprompt を最速で受け取る
scripts/sync-vendor.mjs node_modules → public/vendor/
scripts/optimize-icons.mjs アイコンの圧縮とサイズ生成
scripts/make-maskable.mjs maskable アイコンの生成
scripts/check-project.mjs 品質ゲート
scripts/measure/ 表示・アクセシビリティの実測用ツール
quality.config.json 品質ゲートの設定
.env.example 環境変数の設定例(VITE_GOOGLE_CLIENT_ID)
.assets-original/ 圧縮前のアイコン原本(git 管理外)
AUDIT.md GIGA Standard v4 の監査記録
ROLLOUT.md 他リポジトリへ展開できる知見の記録
GOOGLE_DRIVE_SETUP.md Googleドライブ連携の設定手順
MANUAL.md 先生向けマニュアル(これまでの版)
docs/manual/ 使い方マニュアルの正本(manual.md + images/。ポータルが取りこむ)
docs/note/ 紹介記事の原稿
src/App.jsxの分割について 定数・スタンプ定義・fabric.js まわりの道具・画面の枠は、それぞれsrc/constants.js/src/data//src/lib//src/components/へ分けてあります。src/App.jsxに残っているのは「アプリの流れ」で、約 2,700 行 / 約 143KB (品質ゲートの基準である 5,000 行 / 400KB 以内)です。 保守性の観点では、さらにsrc/features/(textbook/canvas/share/drive/stamps)へ分けるのが望ましいものの、状態が広く共有されているため 機械的に切ると壊れます。1 機能ずつ、合意のうえで分けることを提案します。
このアプリケーションはバックエンドサーバーを必要とせず、静的ファイルのみで動作します(GitHub PagesやVercel、Cloudflare Pagesなどにデプロイ可能です)。
-
リポジトリをクローンします。
git clone https://github.com/GIGAyama/Digital_textbook.git cd Digital_textbook -
パッケージをインストールします。
npm ci -
開発サーバーを起動します。
npm run dev -
ブラウザで
http://localhost:5173/にアクセスします (vite.config.jsのBASEは相対パス./なので、末尾にリポジトリ名は付きません)。
開発サーバーでも manifest と Service Worker を配るようにしてあるため
(vite.config.js の devOptions)、npm run dev のままでも
アドレスバー右端のインストールボタンと、アプリ内の「アプリを入れる」ボタンが出ます。
どちらもページの load の後にブラウザが判定するので、表示まで数秒かかります。
Service Worker が古いまま残って挙動が変わったときは、
DevTools の Application → Service Workers で「Unregister」してから再読み込みしてください。
Googleドライブ同期をローカルで試す場合は、.env.example を .env にコピーして
VITE_GOOGLE_CLIENT_ID を設定してください(.env は git 管理外です)。
あわせて、Google Cloud の「承認済みの JavaScript 生成元」に http://localhost:5173 を追加してください
(登録していないオリジンから接続すると、Google の画面で「エラー 400: origin_mismatch」になります)。
外部ライブラリについて pdf.js / fabric.js / idb-keyval / jsQR / PeerJS / qrcode は npm でバージョンを固定し、
scripts/sync-vendor.mjsがpublic/vendor/へ展開します(npm run dev/npm run buildの前に自動で走ります)。合計約 2MB あるため git にはコミットしていません。npm ciを実行すれば再現されます。 以前は cdnjs / jsDelivr から実行時に読み込んでいましたが、integrityが無く配信元の改ざんを検知できないこと、学校のフィルタリングが CDN を塞ぐと起動できないこと、CSP を厳しくできないことから、自己ホストに切り替えました。
- 変更をコミットし、
mainにマージします。 package.jsonのversionを上げます(例1.0.0→1.1.0)。 Service Worker のキャッシュは Workbox が各ファイルのハッシュで管理するため、 ビルドし直せば自動的に新しい版が配られます。versionは 「どの版が配布されているか」を人間が追うためのものです。mainへの push で.github/workflows/deploy.ymlが動き、品質ゲート → ビルド → GitHub Pages(gh-pagesブランチ)へ反映されます。 Googleドライブ同期を使う場合は、リポジトリの Settings → Secrets and variables → Actions → Variables にVITE_GOOGLE_CLIENT_IDを登録しておきます。- 反映後、実機で次を確認します。
- 「あたらしい バージョンが あります」の帯が出て、押すと新しい版になる
- DevTools の Application タブで manifest が読め、インストール可能と表示される
- オフラインにして再読み込みしても起動する
npm run check # ESLint + GIGA Standard v4 Part I の検査
npm run build # 本番ビルド(前に vendor の同期が走る)
Part I(表示・PWA・セキュリティ・性能)の要件を機械的に検査します。
Pull Request と main 以外への push では .github/workflows/ci.yml が、
main への push では deploy.yml が、いずれも npm run build の前にこれを実行します。
検査項目と、意図的に許可している例外は quality.config.json に定義しています。
検査に落ちたときは、検査をゆるめるのではなく securityExceptions / exceptions に理由を書いて明示的に許可してください。
-
本アプリは、ユーザーがアップロードしたPDFや書き込みデータを外部のクラウドサーバーに一切送信・保存しません。
-
全てのデータはユーザーのデバイス上(ブラウザのIndexedDB / localStorage)にのみ保存されます。Googleドライブ同期を使う場合のみ、先生自身のドライブへ保存されます。
-
共有機能(P2P通信)を利用する際のシグナリング(接続先の仲介)にのみPeerJSのパブリックサーバーを使用しますが、実際のデータ通信は端末間で直接(エンドツーエンド暗号化で)行われます。
-
QRコードから読み取ったURLは、
http://https://で始まるものだけを新しいタブ(noopener)で開きます。それ以外のスキームは開かず、内容を表示するだけです。
サーバーを持たない設計のため、守るべき境界は「このアプリが何を読み込み、どこへ送るか」の一点に集約されます。index.html の Content-Security-Policy で、そこを明示的に絞っています。
| 種別 | 許可している先 | 理由 |
|---|---|---|
default-src |
'self' |
既定はすべて自分のサイトのみ |
script-src |
'self' / https://accounts.google.com |
ライブラリは全て自己ホスト。外部スクリプトは Google ドライブ連携のログイン部品のみ |
connect-src |
'self' / data: / blob: / https://www.googleapis.com / https://accounts.google.com / https://0.peerjs.com(wss: 含む) |
ドライブ API と P2P のシグナリングのみ。ワイルドカードは使っていない |
style-src |
'self' / 'unsafe-inline' / https://fonts.googleapis.com |
React と fabric.js が要素へ直接 style を書き込むため 'unsafe-inline' は外せない |
font-src |
'self' / https://fonts.gstatic.com / data: |
下記「日本語フォント」を参照 |
img-src |
'self' / data: / blob: |
PDFのページ画像は data: URL として端末内に保持する |
media-src |
'self' / https: / data: / blob: |
先生が貼り付けた音声ファイルの URL を再生する機能があるため |
frame-src |
https://accounts.google.com |
Google のログイン画面のみ |
worker-src |
'self' / blob: |
PDF.js のワーカーのため |
object-src |
'none' |
プラグインは一切使わない |
base-uri / form-action |
'self' |
差し込みによる送信先のすり替えを防ぐ |
日本語フォントを自己ホストしていない理由:Zen Maru Gothic は Google Fonts 側で unicode-range により約 120 個に分割配信されており、ブラウザは実際に使う数個(各 20KB 前後)だけを取得します。全字形を自前で持つと数 MB になり、逆にサブセットを切ると児童が入力した漢字が豆腐(□)になる恐れがあります。分割配信の仕組みに任せるのが最も安全と判断し、font-src / style-src で 2 ホストだけを許可しています。フォントが取得できない環境では、端末に入っている丸ゴシック系へ自動的にフォールバックします。
Google ドライブ連携で要求するのは https://www.googleapis.com/auth/drive.file のみです。これは このアプリが作成したファイルにしかアクセスできない スコープで、auth/drive(ドライブ全体)は要求していません。品質ゲートでも、auth/drive などの広すぎるスコープが混入していないかを検査しています。
なお VITE_GOOGLE_CLIENT_ID に入れる OAuth クライアント ID は秘密の鍵ではありません。Web 向けのクライアント ID はどのみちブラウザの通信に現れる公開の識別子で、これを知られてもデータは読めません。なりすましを防いでいるのは Google Cloud 側の 「承認済みの JavaScript 生成元」 の設定です。公開先ドメインを必ず登録してください(→ GOOGLE_DRIVE_SETUP.md)。一方 クライアントシークレットは本物の鍵なので、このアプリでは使用せず、リポジトリにも置かないでください。
| 項目 | 制限 | 超えるとどうなるか |
|---|---|---|
| 端末内の保存容量 | ブラウザが割り当てる IndexedDB の枠(Chrome は概ね空きディスクの 60%) | 保存に失敗し、書き込みが残らない |
| PDF 1 冊のページ数 | 実用上 300 ページ程度 | メモリ4GBの Chromebook では取り込み中にタブが破棄されることがある |
| PDF の暗号化 | パスワード付きPDFは非対応 | 「PDFの読み込みに失敗しました」と表示される |
| 1 ページの解像度 | 取り込み時に PDF_RENDER_SCALE = 2(A4 で 1190×1684px、JPEG 品質 0.85) |
上げると鮮明になるが、保存容量と P2P 配布時間が増える |
| 元に戻せる回数 | 1ページあたり 30 手 | それ以上前には戻せない(ページを移動すると履歴は作り直される) |
| P2P の同時配布 | 実用上 10 端末程度 | シグナリングが詰まり、接続に失敗する端末が出る |
| PeerJS シグナリング | 公開サーバー(0.peerjs.com)の無償枠 |
混雑時は接続に失敗する。データ本体は経由しない |
| Google ドライブ API | Google の無償クォータ | 保存・復元が一時的に失敗する。時間を置けば回復する |
| iOS Safari の ITP | ホーム画面に未追加だと 7 日で保存データが削除される | 書き込みが消える(→ MANUAL.md の「アプリとして入れる」) |
manifest.webmanifestのid/scope/start_urlは、すべて相対パス./に揃えています。 独自ドメイン(digital-textbook.giga-school.com)ではアプリがオリジンの直下で配信されるため、リポジトリ名の絶対パス(旧/Digital_textbook/)のままだと資産が 404 になり、scopeがページの URL を含まなくなって PWA としてインストールできません。 このリポジトリをコピーして新しいアプリを作るときは、vite.config.jsのBASEを最初に書き換えてください。- Service Worker は Workbox(
vite-plugin-pwa)が生成します。localStorageには一切触れません。キャッシュの掃除も Workbox が自分の管理下のものだけを対象にするため、同一オリジンの他アプリを壊しません。 public/vendor/(約 2MB)は先読みキャッシュから除外し、実際に使う時点で取得して以後キャッシュします。ここを先読みに入れると、40 人が同時に開く校内 Wi-Fi で初回表示が止まります。- 更新は
registerType: 'prompt'です。新しい版が用意できると「あたらしい バージョンが あります」の帯が出て、押してもらってから切り替えます。書き込みの途中で黙ってリロードされるのを避けるためです。 offline.htmlを同梱しています。圏外でも「壊れた」と思わせない案内を、アプリと同じ配色・フォントで表示します。- iOS Safari には
beforeinstallpromptがありません。「共有 → ホーム画面に追加」の手順は MANUAL.md に記載しています。 devOptionsで、開発サーバーでも manifest と Service Worker を配っています。 既定では本番ビルドのときにしか出ないため、npm run devの間は<link rel="manifest">が入らず/sw.jsも引けません。 ブラウザから見るとインストールできるサイトの条件を満たさないので、 アドレスバーのインストールボタンが開発中はいつまでも出ず、beforeinstallpromptも飛ばないためアプリ内の「アプリを入れる」ボタンも出ませんでした。 インストールまわりを直すたびに毎回ビルドして確かめるのは現実的ではないので、 開発時も本番と同じ形を配ります。
GIGA山
- ホームページ: https://giga-school.com
This project is licensed under the MIT License.