はじめに(背景)
実務で「QRコードをスキャンしたら、画面操作なしで即座にレーザー/インクジェットプリンタから印刷を出したい」という要件があり、Webブラウザからローカルプリンターを制御できる「QZ Tray」を採用しました。
しかし、いざ実装してみるとセキュリティ周りの設定(特に接続時のポップアップ表示)で非常に苦戦しました。ローカル環境では動いても、クラウド環境にデプロイした途端に動かなくなるなど、公式の英語ドキュメントだけでは分かりづらかった知見が多かったため、備忘録として残します。
今回は、完全自動印刷(サイレント印刷)に不可欠な「接続ポップアップを回避するための設定(Connect編)」を解説します。
本記事では証明書および秘密鍵の作成方法は扱いません。私は公式の生成サービスは利用せず独自に作成した鍵を使用しましたが、手順を再現できる状態ではないため割愛します。
QZ Trayのデフォルトの挙動と課題
QZ Trayはデフォルトの「証明書なし」の状態(resolve() するだけの実装)だと、ブラウザから接続するたびにクライアント側で「このサイトからの印刷を許可しますか?」というような確認ポップアップが表示されます。
これでは「QRスキャン即印刷」のような、現場の手間を減らすための自動化要件を満たせません。接続時の確認ダイアログを抑制するには、デジタル署名(Signature)を用いた信頼関係の構築が必要になります。
ポップアップを回避する3つの実装ステップ
フロントエンド(JavaScript)側で行う主な設定は以下の3つです。
なお、これらのコールバックは QZ Tray が署名や証明書を必要とするタイミングで自動的に呼び出されます。
1. setCertificatePromise のオーバーライド
まず、信頼された公開証明書を読み込ませるためにプロミスをオーバーライドします。
// 1. 公開証明書のセット
qz.security.setCertificatePromise((resolve) => {
// 実際にはここに生成した証明書の文字列(PEM形式など)を渡します
const cert = "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----";
resolve(cert);
});
2. 署名アルゴリズムの指定
署名生成時に使用するアルゴリズムを指定します。
今回は SHA512 を使用します。
// 2. 署名アルゴリズムをセット
qz.security.setSignatureAlgorithm("SHA512");
3. setSignaturePromise の実装(サーバーとの連携)
ここが一番の核心です。実際の運用では、セキュリティのためにブラウザ側ではなく、サーバー側(バックエンド)でプライベートキー(秘密鍵)を使って署名処理を行い、その結果をフロントに返します。
実運用では、フロント側は fetch 等を用いてバックエンドAPIへ署名を依頼する構成が一般的です。
// 3. サーバーサイドに署名を要求するプロミスを設定
qz.security.setSignaturePromise((toSign) => {
return new Promise((resolve, reject) => {
// 自作のバックエンドAPIに署名対象の文字列(toSign)を送信
fetch('/api/qz/sign', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ requestString: toSign })
})
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.text(); // サーバーから返ってきたBase64エンコード済みの署名
})
.then(signature => {
resolve(signature); // サーバーで生成した署名をQZ Trayへ渡す
})
.catch(err => {
console.error('QZ Tray Signature Error:', err);
reject(err);
});
});
});
※サーバー側(バックエンド)では、受け取った requestString に対して秘密鍵でデジタル署名を行い、Base64エンコードして返すAPIを実装しておく必要があります。
フロント側のjavascriptで秘密鍵を使用して署名を行うことも可能ですが、その場合秘密鍵を公開してしまうことになるため、不特定多数が利用するようなサービスでは注意が必要です。
おわりに
公式ドキュメントにも記載されていますが、実際に実装するまで「どのタイミングで何が呼ばれるのか」が理解しづらく、動作確認にも苦労しました。
私の環境では、証明書を設定しただけではサイレント印刷にならず、クライアント側で一度信頼設定が必要でした。また、ローカル環境で問題なく動作していた構成が、クラウド環境へデプロイした途端に期待通り動作しなくなりました。
最終的には、実際に利用する証明書と本番相当の環境で検証することで解決しましたが、当初はローカル環境でのみ動作確認を行っていたため、原因の切り分けにかなり時間を要しました。
QZ Trayには詳細なログをリアルタイムで確認できる機能があります(Diagnostic→View logs)。JavaScript側のログと合わせて確認することで、どの処理で問題が発生しているのかを追いやすくなります。トラブルシューティング時には積極的に活用することをおすすめします。
動作確認の環境構築も含めて、QZ Trayのセキュリティ周りは一筋縄ではいかない部分が多いです。しかし、この署名処理を正しく実装できれば、Webからローカルプリンターへの高速なサイレント印刷という強力な仕組みを実現できます。
QZ Trayで苦戦している方の参考になれば幸いです。