ガルーンで予定のリマインダーをブラウザのみで実施する

Cybozuのガルーンは、PCのCybozu Desktop2やスマホのKUNAIアプリに予定の通知機能はあるものの、 ブラウザでのNotification APIを使った通知機能は実装されていない(2023-01-24時点)。

そこで、JavaScript / CSSによるカスタマイズ機能を使って予定の時刻に迫った時に通知を表示させてみた(管理者向け)。

実装

使うJavaScriptファイルは以下の内容の1ファイル。適当に拡張子.jsをつけて保存する。

(async function notifyGaroonEvent(){
    const TIME_FETCH_INTERVAL = 10 * 60 * 1000; // 予定の再取得をする間隔 (10分)
    const TIME_NOTIFY_BEFORE = 3 * 60 * 1000; // 予定の開始時刻の何ミリ秒前に通知するか (3分)
    const TIME_NOTIFY_CLOSE = 30 * 1000; // 予定の通知を何ミリ秒後に閉じるか (30秒)
    const NOTIFIED_EVENT_IDS_KEY = "notified_ids";
    const timers = [];

    // 通知の許可をユーザに訪ねる
    if(Notification.permission === "default"){
        Notification.requestPermission();
    }
    // 複数タブで開いた場合に複数の通知が飛ばないようにlocalStorageにすでに通知済みのevent_idを登録して管理
    if(localStorage.getItem(NOTIFIED_EVENT_IDS_KEY) === null){
        localStorage.setItem(NOTIFIED_EVENT_IDS_KEY, "[]");
    }

    const icon_url = getComputedStyle(document.querySelector(".icon-appMenu-schedule")).backgroundImage.replace(/^.*"(.*)".*$/, "$1");

    const date_start = (new Date(Date.now())).toISOString();
    const date_end =  (new Date(Date.now() + 86400000)).toISOString();

    garoon.api(`/api/v1/schedule/events?rangeStart=${date_start}&rangeEnd=${date_end}&showPrivate=true`, "GET", {}, (resp) => {
        // 予定取得成功

        // 既存のタイマをクリア
        timers.map(e => clearTimeout(e));
        timers.length = 0;

        // 全日予定となっている予定以外の予定に対してタイマをセット
        for(const evt of resp.data.events.filter(e => e.isAllDay === false)){
            console.log(evt);
            const timediff = Date.parse(evt.start.dateTime) - Date.now();

            // TIME_NOTIFY_BEFORE以降に予定があれば、通知するようにsetTimeout
            if(TIME_NOTIFY_BEFORE < timediff){
                timers.push(setTimeout(function(){
                    const notified_ids = JSON.parse(localStorage.getItem(NOTIFIED_EVENT_IDS_KEY));

                    if(!notified_ids.includes(evt.id)){
                        // 通知済みevent_idをlocalStorageに保存
                        notified_ids.push(evt.id);
                        localStorage.setItem(NOTIFIED_EVENT_IDS_KEY, JSON.stringify(notified_ids));

                        // 通知を表示
                        const n = new Notification(evt.subject, {
                            body: evt.eventMenu + ": " + evt.subject,
                            icon: icon_url,
                            tag: evt.id
                        });
                        n.onclick = function(e){
                            e.preventDefault();
                            window.open("https://" + location.host + "/g/schedule/view.csp?event=" + evt.id, "_blank");
                        };
                        // 通知を自動消去
                        setTimeout(n.close.bind(n), TIME_NOTIFY_CLOSE);
                    }
                }, timediff - TIME_NOTIFY_BEFORE));

                console.log("Set timer: " + (timediff - TIME_NOTIFY_BEFORE));
            }
        }
    }, (error) => {
        // エラー
        console.log(error);
    });

    // TIME_FETCH_INTERVAL後に再度予定を取得
    setTimeout(notifyGaroonEvent, TIME_FETCH_INTERVAL);
})();

上記のコードはクラウド版を前提にしているので、オンプレミス環境の場合はURLを適宜変更する。

あとはスケジュールのカスタマイズ | クラウド版 Garoon ヘルプに従って作業する。

途中で保存したJavaScriptファイルをアップロードすればOK。CSSは不要。

動作

カスタマイズ反映後の最初のアクセス時は、ブラウザが通知を許可するかどうかを訪ねてくるので「許可」する。

あとは適当に予定を作成すると、その開始時刻の3分前に以下のような通知が出てくるようになる(ChromeとEdgeでは動作を確認したが、Firefoxも動作するはず)。

ただし、ブラウザでガルーンのタブを開きっぱなしにしていないと通知は出ない点は注意。

また、SetTimeoutは正確に指定時刻後に実行されることを保証するわけではないが、だいたいうまく動作している。