
ふぁーびー
2025/12/12

この記事で紹介する手法は、kintoneの標準機能や公式なAPIとして公開されていない、非公式な手法となります。kintoneのアップデートにより動作しなくなる可能性があります。
私のチームでは日頃の業務をkintoneアプリで行っており、アプリの管理については「アプリ管理権限を複数人に開放し、気付いた人が改善する」 という運用をしています。
最初のうちは改善したらチャットツールやkintoneのどこかしらで変更内容を共有して、、という運用をしていましたが、アプリも人も増えてくると「いつの間にかこれ変わってるけど、いつ誰が、、?」ということが多々発生するようになりました。
そこで、変更管理アプリを作成し、「変更点を手動で記録する」という運用ルールでカバーしていました。しかし、これはあくまで 「人の意識」 に依存した運用です。
「ちょっとフィールド動かしただけだから」と記録を後回しにして忘れる
記録はしたものの、内容に抜け漏れや誤りがある
記録作業自体が面倒
といった課題がまた出てきました。そこで「人の意識を介さず、バックグラウンドで勝手に(無意識に)変更が検知・記録される仕組み」 を作ることにしました。
本記事では「無意識に」をどう実現するかをメインに書きます。
目指すゴールは以下の通りです。
ユーザーはいつも通り kintone の画面で「アプリを更新」ボタンを押すだけ。
更新が完了した瞬間、システムがそれを検知する。
「変更後の設計情報」 が自動的に構成管理アプリに登録される。
アプリの更新完了については、kintone JavaScript API イベントやWebhookでは提供されていません。
またアプリの設定画面にはJavaScriptカスタマイズを適用することはできません。
そこで今回はGoogle Chromeの拡張機能であるTampermonkeyを利用し、オリジナルのJavaScriptを適用させます。
次に何をもって「アプリ更新完了」とみなすかですが、単に「アプリを更新」ボタンのクリックイベント (click) を拾うだけでは、クリック後にバリデーションエラーで更新が失敗する可能性などが考えられます。
DevToolsで通信を解析した結果、以下の流れで内部APIによる処理が行われていることが分かりました。
deploy.json (POST): 更新予約(ここにはAppIDが含まれる)。
getDeployProgress.json (POST): 進捗確認(ポーリングされ、完了すると status: "SUCCESS" が返る)。
そこで XMLHttpRequest をモンキーパッチし、「deploy.json で対象アプリを特定」し、「getDeployProgress.json で完了を見届ける」 というロジックを組みました。
Geminiに生成させたサンプルコードは以下の通りです。
(先述のTampermonkeyで適用し動作することは確認しておりますが、ご利用に際しては自己責任でお願いします)
(function() {
console.log("%c[AutoVC] 監視を開始しました", "color: white; background: #008080; padding: 4px; border-radius: 4px;");
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
// 状態保持用
let pendingAppId = null; // 更新対象のアプリID
let pollCount = 0; // ポーリング回数のカウント
// 1. open: URLを記録
XMLHttpRequest.prototype.open = function(method, url) {
this._url = url;
this._method = method;
return originalOpen.apply(this, arguments);
};
// 2. send: 通信内容(body)を捕捉し、レスポンスを監視
XMLHttpRequest.prototype.send = function(body) {
// send実行時の `body` (送信データ) はここで確保される
const payloadData = body;
this.addEventListener('load', function() {
try {
if (!this.responseText) return;
// URL判定
const isDeploy = this._url && this._url.includes('deploy.json');
const isProgress = this._url && this._url.includes('getDeployProgress.json');
// ---------------------------------------------------
// A. デプロイ開始 (deploy.json)
// ---------------------------------------------------
if (isDeploy && this._method.toUpperCase() === 'POST' && this.status === 200) {
// 1. レスポンスで成功を確認 {"success": true}
const res = JSON.parse(this.responseText);
if (!res.success) {
console.warn("[AutoVC] deploy.jsonが呼ばれましたが success:true ではありません");
return;
}
// 2. リクエストペイロード(送信データ)からアプリIDを取得
// 形式: {"app": "1", "__REQUEST_TOKEN__": "..."}
if (payloadData) {
const req = JSON.parse(payloadData);
// アプリIDを取得(念のため数値型でも文字列型でも対応)
if (req.app) {
pendingAppId = String(req.app);
pollCount = 0;
console.log(`%c[AutoVC] デプロイ開始を検知 (AppID: ${pendingAppId})`, "color: white; background: green; font-weight: bold; padding: 2px 5px;");
console.log("Request Payload:", req);
}
}
return; // 処理終了
}
// ---------------------------------------------------
// B. 進捗確認 (getDeployProgress.json)
// ---------------------------------------------------
if (isProgress && this.status === 200) {
// 【早期リターン】自分が開始したデプロイでなければ、処理せず終了
if (!pendingAppId) {
return;
}
// カウントアップ
pollCount++;
// グループ化してログ出力
console.groupCollapsed(`[AutoVC] 進捗確認 #${pollCount} (Target: ${pendingAppId})`);
const res = JSON.parse(this.responseText);
console.log("Response Body:", res);
// JSON構造チェック (res.result.item)
if (!res.result || !res.result.item) {
console.warn("想定外のJSON構造 (result.itemなし) -> スキップ");
console.groupEnd();
return;
}
const item = res.result.item;
const currentStatus = item.status;
const resAppId = String(item.appId);
console.log(`Status: %c${currentStatus}`, "font-weight: bold; color: #d35400;", `(ResAppID: ${resAppId})`);
// アプリIDの一致確認(念の為)
if (resAppId !== pendingAppId) {
console.warn(`ID不一致のため無視します (Target: ${pendingAppId} vs Res: ${resAppId})`);
console.groupEnd();
return;
}
// 成功判定 (SUCCESS または COMPLETED)
const isSuccess = (currentStatus === 'SUCCESS' || currentStatus === 'COMPLETED');
if (isSuccess) {
console.log("%cデプロイ完了!", "color: blue; font-weight: bold; font-size: 1.2em;");
alert(`【更新完了】\nアプリ(ID:${pendingAppId})の反映が完了しました!\n(確認回数: ${pollCount}, Rev: ${item.revision})`);
// 完了処理
pendingAppId = null;
pollCount = 0;
} else if (currentStatus === 'FAIL' || currentStatus === 'CANCELLED') {
console.error("デプロイ失敗またはキャンセルされました。");
pendingAppId = null;
pollCount = 0;
} else {
console.log("まだ処理中です...");
}
console.groupEnd();
}
} catch(e) {
console.error("[AutoVC] エラー発生:", e);
// グループが開いていれば閉じる安全策
try { console.groupEnd(); } catch(err){}
}
});
return originalSend.apply(this, arguments);
};
})();
重要なポイントだけピックアップします。
kintone内部の通信を捕捉するため、ブラウザのネイティブオブジェクトである XMLHttpRequest をオーバーライド(Monkey Patch)しています。
prototype.open のラップ:
リクエスト先のURL(エンドポイント)をインスタンスプロパティ _url として保持し、後続の処理で判定に使用します。
prototype.send のラップ:
リクエストボディ(Payload)を捕捉し、変種に保存(deploy.json 実行時のApp ID特定に使用)。
load イベントリスナーを注入し、サーバーからのレスポンス(ステータスコードやレスポンスボディ)を非同期で傍受します。
kintoneのアプリ更新プロセスは「アプリ更新をリクエスト」して「更新完了まで待つ」という流れになります。
REST APIでもアプリの設定を運用環境へ反映するAPIとアプリの設定の運用環境への反映状況を確認するAPIがわかれているので、画面での操作も同じような感じですね。
※このあたりのことは過去に記事を書いているのでよければご覧ください
kintone REST APIでアプリ間連携(ルックアップ/関連レコード/アクション)を含むアプリを作成する #kintoneRestApi - Qiita
deploy.jsonへのリクエスト → アプリの設定を運用環境へ反映するAPIgetDeployProgress.json → アプリの設定の運用環境への反映状況を確認するAPI
という対応になっているようなので、この2つへの通信のペイロードおよびレスポンスを見て、最終的には反映状況が「SUCCESS」となることを待ち、「更新完了」とみなしています。
ここまでできれば、後は更新完了時に各種設計情報をAPIで取得してkintoneへ(ここはなんでもいいですが)貯めていくことで、
いつ誰がどこをどう変えたのかの確認
最新バージョンとある時点のバージョンの差分は何かの確認
Webhook等用いて危険な変更が発生していないかを検知
といったデータの活用もでき、また「変更の目的や背景」も入力させて設計内容とセットで貯めておくことができれば、より管理がしやすくなりそうです。
(AIに食わせてナレッジ収集など、、面白いこともできそうですね、、👀)
現状の仕組みは、アプリ更新を行う全ユーザーがこのスクリプトを実行していないと、監視の目をすり抜けて更新が可能です。
これを防ぐためには、このスクリプトが実行されていないとkintoneを使えないといった仕組みを全体カスタマイズと組み合わせることによって実現するなど、検討する必要がありそうです🤔
・・・と、ここまでハックしておいて何ですが、やはり公式の機能で実現できるのが一番です。
「アプリ更新完了」を検知できるイベントやWebhook が公式機能として実装されることを楽しみにしています🥳
昨年はこんな記事を書いたんですが、(たったの)1年経った今ではバックエンドも含めてVibe Codingでサクッと作れるような事態になっており、流れの爆速さに息切れ気味です・・・今年もAIがらみのことを書こうと思いましたが、kintoneをハックしてみました。(結局コードはAIが書いてますが)
来年もよろしくお願いします。
カテゴリー
最新の投稿
2026/08/11
人が減る日本だからこそ、AIとの「協奏」が未来をつくる。
2026/08/10
kintoneアプリが1時間で完成する理由|FLASH AIサービスの仕組み
2026/07/14
kintoneをお試しで使い始めた方へ|失敗しないための「3つのポイント 」
2026/06/16
【後編】AI時代、SIerの価値はどこに残るのか?関東・関西のkintoneトップランナーが語る「共創」の未来
2026/06/12
【前編】AI時代、SIerの価値はどこに残るのか?関東・関西のkintoneトップランナーが語る「共創」の未来
2026/06/03
【管理職向け】「AIとつくる」業務効率化をゴールにした教えないkintone人材育成法!
2026/05/15
元ウェディングスタイリストがITの世界で描く「お客様の喜び」のその先【プロダクト営業 ぱんちゃん】
2026/05/13
【今春、情シス担当になったあなたへ】ブラックボックス化したkintoneアプリに悩まされてませんか?AIがボタン1つで解決!
2026/04/28
タイで30人の部下を率いたマーケターが、なぜ日本のITベンチャーへ?国境を越えた挑戦の理由【マーケ・広報 カーン】
2026/04/21
レクシンAIで「今いる社員」をIT人材に変える経営戦略!