PageSourceSearch

https://smartkintai.ai/sw.js

js smartkintai.ai collected 2026-10-05 15:06:46 UTC 3,349 bytes, 111 lines download raw bytes

1// Service Worker for Push Notifications
2// TimeCard マルチテナント勤怠管理システム
3
4const NOTIFICATION_TAG = 'timecard-notification';
5
6/**
7 * プッシュ通知受信時の処理
8 */
9self.addEventListener('push', (event) => {
10  console.log('[Service Worker] Push notification received');
11
12  if (!event.data) {
13    console.warn('[Service Worker] Push event has no data');
14    return;
15  }
16
17  try {
18    const data = event.data.json();
19    console.log('[Service Worker] Push data:', data);
20
21    const options = {
22      body: data.body || '新しい通知があります',
23      icon: data.icon || '/icons/icon-192x192.png',
24      badge: data.badge || '/icons/icon-192x192.png',
25      tag: NOTIFICATION_TAG,
26      requireInteraction: true, // ユーザーが操作するまで表示し続ける
27      data: data.data || {},
28      actions: [
29        {
30          action: 'view',
31          title: '確認する',
32          icon: '/icons/icon-192x192.png',
33        },
34        {
35          action: 'close',
36          title: '閉じる',
37        },
38      ],
39      // 振動パターン(モバイル用): 200ms振動, 100ms停止, 200ms振動
40      vibrate: [200, 100, 200],
41      // 音(システムデフォルト)
42      silent: false,
43    };
44
45    event.waitUntil(
46      self.registration.showNotification(data.title || '勤怠管理システム', options)
47    );
48  } catch (error) {
49    console.error('[Service Worker] Error parsing push data:', error);
50  }
51});
52
53/**
54 * 通知クリック時の処理
55 */
56self.addEventListener('notificationclick', (event) => {
57  console.log('[Service Worker] Notification clicked:', event.action);
58
59  event.notification.close();
60
61  if (event.action === 'close') {
62    // 「閉じる」ボタン - 何もしない
63    return;
64  }
65
66  // 「確認する」ボタン または 通知本体クリック
67  const urlToOpen = event.notification.data?.url || '/';
68  const fullUrl = new URL(urlToOpen, self.location.origin).href;
69
70  event.waitUntil(
71    clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => {
72      // 同じオリジンのウィンドウが既にあればフォーカスのみ(navigate は Android で白画面になる)
73      for (const client of clientList) {
74        if (client.url.startsWith(self.location.origin) && 'focus' in client) {
75          return client.focus();
76        }
77      }
78
79      // ウィンドウが存在しない場合のみ新規オープン
80      if (clients.openWindow) {
81        return clients.openWindow(fullUrl);
82      }
83    })
84  );
85});
86
87/**
88 * プッシュ購読変更時の処理
89 * 購読の更新はアプリ側(Punch.vue)で行うため、ここでは何もしない
90 */
91self.addEventListener('pushsubscriptionchange', (_event) => {
92  // アプリ起動時に Punch.vue が購読状態を確認して再登録する
93});
94
95/**
96 * Service Worker インストール時
97 */
98self.addEventListener('install', (event) => {
99  console.log('[Service Worker] Installing...');
100  self.skipWaiting(); // 即座にアクティブ化
101});
102
103/**
104 * Service Worker アクティベーション時
105 */
106self.addEventListener('activate', (event) => {
107  console.log('[Service Worker] Activating...');
108  event.waitUntil(clients.claim()); // 既存のタブを即座に制御下に置く
109});
110
111console.log('[Service Worker] Loaded successfully');

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.