0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

kintone のアプリ間移動が遅いので、Ctrl+/ で設定画面まで直行する Chrome 拡張を作った

0
Posted at

kintone のアプリが 50 を超えたあたりから、「目的のアプリの、目的の設定画面にたどり着くまで」がいちばんの手間になりました。

アクセス権を付けてほしいと頼まれたら、アプリ一覧から探して、歯車を押して、設定タブを開いて、アクセス権を開く。JavaScript を直したら、また同じ道をたどってカスタマイズ画面へ降りていく。1回は数秒でも、1日に何十回もあると手が止まります。

そこで、kintone のどの画面からでも Ctrl+/ でパレットを開き、名前を数文字打って Enter で目的の画面に飛べる Chrome 拡張を作りました。この記事では、何に困って、Manifest V3 でどう作って、半年使って何が残ったかを書きます。

困っていたこと

海外の工場で、kintone をひとりで作って運用しています。アプリは 50 を超えました。

困りごとは大きく3つでした。

1つ目はアプリ間の移動です。アプリ一覧から探すより、名前を打つほうが早い数になっていました。

2つ目は設定画面の深さです。アクセス権・通知・JavaScript / CSS カスタマイズ・プラグイン設定は、どれもアプリの設定の奥にあります。開発中はコードを直してアップロードして確かめる往復のたびに、ここを降りていきます。

3つ目はフィールドコードです。最近はカスタマイズのコードを AI に書いてもらうことが増えましたが、フィールドコードを1つ間違えられると動かないコードが返ってきます。毎回フォームの設定画面を開いてコードを書き写すのは、移動以上に面倒でした。

作ったもの

Ctrl+/(Mac は Cmd+/)でパレットが開きます。

アプリ名の一部を打てばアプリに移動します。設定画面は英語の短い言葉で呼べるようにしました。アクセス権は acl、JavaScript / CSS カスタマイズは js、通知は notify、今開いているレコードの再利用は dup です。日本語だと変換を挟むので、半角のまま3文字打って Enter のほうが手が止まりません。

スクリーンショット 2026-10-05 午後9.26.41.png

AI に相談するときは、パレットの「フィールドコード一覧をコピー」を使います。1行目にアプリ ID、続けてフィールドコード・フィールド名・種類がタブ区切りで入るので、そのまま会話に貼れます。込み入った相談のときは「フォーム定義を JSON で取得」で、選択肢やルックアップの設定まで含めたフォームの定義を丸ごと貼ります。

Manifest V3 での作り方

Chrome 拡張から kintone を触るときに引っかかった点を、実装の順に書きます。

content script からは kintone.api が見えない

最初に詰まったのがここでした。content script は「isolated world」という、ページとは別の JavaScript の世界で動きます。DOM は共有されますが、ページ側の window.kintone は見えません。

kintone.api が使えればログイン中のセッションのまま REST API を呼べるので、API トークンを発行してもらう必要がなくなります。そこで、chrome.scripting.executeScript に world: 'MAIN' を指定して、ページ側の世界で関数を実行しています。

// service_worker.js(フィールドコード一覧を取る部分を簡略化)
const [{ result }] = await chrome.scripting.executeScript({
  target: { tabId },
  world: 'MAIN',
  func: async () => {
    const sdk = window.kintone;
    if (!sdk?.app || !sdk?.api) return { ok: false, reason: 'not_kintone_page' };
    const appId = sdk.app.getId();
    const resp = await sdk.api(
      sdk.api.url('/k/v1/app/form/fields', true), 'GET', { app: appId }
    );
    return { ok: true, appId, properties: resp.properties };
  },
});

func に渡した関数はシリアライズされてページに送られるので、外側の変数は参照できません。必要な値は args で渡します。

何度も呼ぶ処理は、web_accessible_resources に置いた小さなスクリプト(page-bridge)をページに差し込み、content script と window.postMessage でやり取りする形にしました。メッセージは location.origin を相手先に指定し、受け取る側でも自分の印が付いたメッセージだけを処理します。

権限は「許可したドメインだけ」にする

kintone のドメインは *.cybozu.com・*.kintone.com・*.kintone-dev.com と複数あり、会社ごとにサブドメインが違います。最初から全部に権限を取ると、ストアの審査でもインストールする人にとっても重くなります。

そこで host_permissions は空にして、optional_host_permissions に候補を並べ、使う人が自分の kintone のドメインを設定画面で許可したときだけ動くようにしました。

"permissions": ["storage", "scripting", "tabs", "alarms", "contextMenus", "sidePanel"],
"host_permissions": [],
"optional_host_permissions": [
  "https://*.kintone.com/*",
  "https://*.cybozu.com/*",
  "https://*.kintone-dev.com/*"
]
// 許可済みならそのまま、未許可ならダイアログを出す
async function ensureOrigin(pattern) {
  if (await chrome.permissions.contains({ origins: [pattern] })) return true;
  return await chrome.permissions.request({ origins: [pattern] });
}

chrome.permissions.request はユーザーの操作(クリック)の中からしか呼べないので、設定画面のボタンから呼んでいます。

状態は service worker に置かない

Manifest V3 の background は service worker で、しばらく操作がないと止まります。変数に持たせた状態は、止まった時点で消えます。

なので、残したいものはすべて chrome.storage に置いています。お気に入りのアプリ・ピン留め・ショートカットのような、量が少なく PC をまたいで同じであってほしいものは chrome.storage.sync。アプリ一覧のキャッシュや件数の前回値のような、その PC だけで持てばよいものは chrome.storage.local です。

sync は容量の上限が小さい(全体で約 100KB、1項目あたり約 8KB)ので、キャッシュを入れるとすぐに溢れます。どちらに置くかは「別の PC でも同じであってほしいか」で決めました。

設定はすべてブラウザの中に保存され、外部のサーバーには送っていません。

サイドパネル

よく使うアプリや、見ておきたい一覧の件数を並べるサイドパネルも付けました。sidePanel 権限を取り、拡張のアイコンを押したら開くようにしています。

chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });

件数の更新は2段に分けました。パネルが開いている間は、パネルのページ側のタイマーで数分おきに取り直します(ページなので、開いている間は止まりません)。パネルを閉じている間の「件数がしきい値を超えたら通知する」確認は、service worker 側で chrome.alarms を使って 10 分おきに起こしています。service worker は止まるので、こちらは setInterval では続きません。

// service_worker.js
chrome.alarms.create('pb-watchlist-threshold-check', { periodInMinutes: 10 });
chrome.alarms.onAlarm.addListener((alarm) => {
  if (alarm.name === 'pb-watchlist-threshold-check') checkWatchlistThresholds();
});

半年使って、残ったもの

作った本人がいちばん使っているのは、結局パレットのアプリ移動と、acl・js での設定画面への直行です。それに、AI に渡すフィールドコード一覧のコピーが加わりました。

一方で、いちばん手をかけたサイドパネルは、朝に件数を確かめる1日1回くらいしか開いていません。

パレットを開くときは、「権限を付けたい」「JS を直したい」と行き先がもう決まっています。サイドパネルは、用事がまだ無いときに何かあるかを見に行く画面です。人は用事があるときにしか画面を開かない、というのが、自分で使ってみて分かったことでした。

使ってみたい方へ

ここに書いた機能は、すべて無料で使えます。設定はブラウザの中だけに保存され、kintone へのアクセスも自分で許可したドメインでだけ動きます。

Chrome ウェブストア:
https://plugbits.app/launcher/go/qiita/

説明ページ: https://plugbits.app/launcher

一覧を Excel のような表で直接編集できる有料の Pro 版(買い切り ¥1,980・14日間の無料お試しあり・月額なし)もありますが、この記事の機能には関係ありません。

「この画面にも一発で飛びたい」というものがあれば、コメントで教えてください。次に足すものの参考にします。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?