PageSourceSearch

https://gamercenter.io/sw.js

js gamercenter.io collected 2026-10-09 17:41:14 UTC 4,547 bytes, 199 lines download raw bytes

1const GC_BROWSER_PUSH_SW_VERSION = "2026-05-29"
2const GC_BROWSER_PUSH_FALLBACK_PATH = "/notifications"
3const GC_BROWSER_PUSH_FALLBACK_TITLE = "Gamer Center"
4const GC_BROWSER_PUSH_FALLBACK_BODY = "You have a new notification"
5
6self.addEventListener("install", (event) => {
7  console.info("[GC_BROWSER_PUSH_SW_INSTALL]", {
8    version: GC_BROWSER_PUSH_SW_VERSION,
9  })
10  event.waitUntil(self.skipWaiting())
11})
12
13self.addEventListener("activate", (event) => {
14  console.info("[GC_BROWSER_PUSH_SW_ACTIVATE]", {
15    version: GC_BROWSER_PUSH_SW_VERSION,
16  })
17  event.waitUntil(self.clients.claim())
18})
19
20self.addEventListener("push", (event) => {
21  const notification = parsePushNotification(event)
22
23  event.waitUntil(
24    self.registration.showNotification(notification.title, notification.options)
25  )
26})
27
28self.addEventListener("notificationclick", (event) => {
29  event.notification.close()
30
31  const path = validateNotificationPath(
32    getString(event.notification.data, "path") ||
33      getString(event.notification.data, "url")
34  )
35
36  event.waitUntil(openNotificationPath(path))
37})
38
39function parsePushNotification(event) {
40  const payload = parsePushPayload(event)
41  const data = getRecord(payload, "data")
42  const notificationId =
43    getString(payload, "notificationId") || getString(data, "notificationId")
44  const path = validateNotificationPath(
45    getString(payload, "path") ||
46      getString(payload, "url") ||
47      getString(data, "path") ||
48      getString(data, "url")
49  )
50  const tag =
51    getString(payload, "tag") ||
52    (notificationId ? `gc-notification:${notificationId}` : undefined)
53
54  return {
55    title: getString(payload, "title") || GC_BROWSER_PUSH_FALLBACK_TITLE,
56    options: {
57      badge: validateOptionalNotificationAssetPath(getString(payload, "badge")),
58      body: getString(payload, "body") || GC_BROWSER_PUSH_FALLBACK_BODY,
59      data: {
60        notificationId,
61        path,
62      },
63      icon: validateOptionalNotificationAssetPath(getString(payload, "icon")),
64      renotify: getBoolean(payload, "renotify"),
65      tag,
66    },
67  }
68}
69
70function parsePushPayload(event) {
71  if (!event.data) {
72    return {}
73  }
74
75  try {
76    const payload = event.data.json()
77    if (isRecord(payload)) {
78      return payload
79    }
80  } catch (error) {
81    console.warn("[GC_BROWSER_PUSH_SW_PAYLOAD_PARSE_ERROR]", {
82      version: GC_BROWSER_PUSH_SW_VERSION,
83    })
84  }
85
86  return {}
87}
88
89async function openNotificationPath(path) {
90  const windowClients = await self.clients.matchAll({
91    includeUncontrolled: true,
92    type: "window",
93  })
94
95  for (const client of windowClients) {
96    const clientUrl = new URL(client.url)
97    const clientPath = `${clientUrl.pathname}${clientUrl.search}${clientUrl.hash}`
98
99    if (clientUrl.origin === self.location.origin && clientPath === path) {
100      return client.focus()
101    }
102  }
103
104  for (const client of windowClients) {
105    const clientUrl = new URL(client.url)
106
107    if (clientUrl.origin === self.location.origin && "navigate" in client) {
108      const navigatedClient = await client.navigate(path)
109
110      if (navigatedClient) {
111        return navigatedClient.focus()
112      }
113    }
114  }
115
116  return self.clients.openWindow(path)
117}
118
119function validateNotificationPath(value) {
120  if (typeof value !== "string") {
121    return GC_BROWSER_PUSH_FALLBACK_PATH
122  }
123
124  const path = value.trim()
125
126  if (!path.startsWith("/")) {
127    return GC_BROWSER_PUSH_FALLBACK_PATH
128  }
129
130  if (path.startsWith("//")) {
131    return GC_BROWSER_PUSH_FALLBACK_PATH
132  }
133
134  if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(path)) {
135    return GC_BROWSER_PUSH_FALLBACK_PATH
136  }
137
138  return path
139}
140
141function validateOptionalNotificationAssetPath(value) {
142  if (typeof value !== "string") {
143    return undefined
144  }
145
146  const path = validateNotificationPath(value)
147
148  if (path === GC_BROWSER_PUSH_FALLBACK_PATH && value.trim() !== path) {
149    return undefined
150  }
151
152  return path
153}
154
155function getRecord(value, key) {
156  if (!isRecord(value)) {
157    return {}
158  }
159
160  const nested = value[key]
161
162  if (!isRecord(nested)) {
163    return {}
164  }
165
166  return nested
167}
168
169function getString(value, key) {
170  if (!isRecord(value)) {
171    return undefined
172  }
173
174  const nextValue = value[key]
175
176  if (typeof nextValue !== "string") {
177    return undefined
178  }
179
180  return nextValue
181}
182
183function getBoolean(value, key) {
184  if (!isRecord(value)) {
185    return undefined
186  }
187
188  const nextValue = value[key]
189
190  if (typeof nextValue !== "boolean") {
191    return undefined
192  }
193
194  return nextValue
195}
196
197function isRecord(value) {
198  return typeof value === "object" && value !== null
199}

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.