{"id":"0b6dab9b-5f28-4559-b757-4f07fcb0d6dc","shortId":"CcUCXg","kind":"skill","title":"cometchat-angular-push","tagline":"Push notifications for CometChat Angular UI Kit v4 in Angular 12-15 projects. Web doesn't have native push — covers @angular/service-worker setup, ngsw-config.json, SwPush subscription, server-side webhook to send pushes, click-through routing via Angular Router, Angular Universa","description":"## Purpose\n\nWeb Push for Angular CometChat integrations. Angular's `@angular/service-worker` package provides `SwPush` — a thin wrapper over the Push API — that integrates with Angular's DI tree and Router, so it's the recommended path over hand-rolled Service Workers.\n\n**Read these other skills first:**\n- `cometchat-angular-core` — UIKitSettingsBuilder, login order\n- `cometchat-angular-patterns` — module setup\n- `cometchat-react-push` — same Web Push spec, React-flavored explanation; shared concepts (VAPID, server-side webhook, iOS PWA caveat) covered there in depth\n\n**Ground truth:**\n- `@angular/service-worker` — https://angular.io/guide/service-worker-intro\n- `SwPush` API — https://angular.io/api/service-worker/SwPush\n- Web Push spec — https://datatracker.ietf.org/doc/html/rfc8030\n\n---\n\n## 1. Architecture (same as web)\n\nSame shape as `cometchat-react-push`: client SW + push server + CometChat webhook. Angular's `SwPush` is the client-side primitive; everything else is the same.\n\n---\n\n## 2. Add @angular/service-worker\n\n```bash\nng add @angular/service-worker\n```\n\nThis:\n- Adds `@angular/service-worker` to dependencies\n- Creates `ngsw-config.json` at project root\n- Imports `ServiceWorkerModule.register('ngsw-worker.js', ...)` in `AppModule`\n- Sets `serviceWorker: true` in `angular.json` build options\n\nVerify in `app.module.ts`:\n\n```ts\n@NgModule({\n  imports: [\n    BrowserModule,\n    ServiceWorkerModule.register('ngsw-worker.js', {\n      enabled: !isDevMode(),                              // SW disabled in dev mode by default\n      registrationStrategy: 'registerWhenStable:30000',\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n---\n\n## 3. ngsw-config.json — minimal config for chat push\n\nThe default config caches assets for offline-first PWAs. For chat push, you mostly need the data-sources for live API calls:\n\n```json\n{\n  \"$schema\": \"./node_modules/@angular/service-worker/config/schema.json\",\n  \"index\": \"/index.html\",\n  \"assetGroups\": [\n    {\n      \"name\": \"app\",\n      \"installMode\": \"prefetch\",\n      \"resources\": {\n        \"files\": [\"/favicon.ico\", \"/index.html\", \"/manifest.webmanifest\", \"/*.css\", \"/*.js\"]\n      }\n    },\n    {\n      \"name\": \"assets\",\n      \"installMode\": \"lazy\",\n      \"updateMode\": \"prefetch\",\n      \"resources\": {\n        \"files\": [\"/assets/**\", \"/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)\"]\n      }\n    }\n  ],\n  \"dataGroups\": [\n    {\n      \"name\": \"cometchat-api\",\n      \"urls\": [\"https://api-*.cometchat.io/**\"],\n      \"cacheConfig\": {\n        \"maxSize\": 100,\n        \"maxAge\": \"0d\",\n        \"strategy\": \"freshness\"\n      }\n    }\n  ]\n}\n```\n\n**Don't cache CometChat API responses** — they're real-time. The freshness strategy with `maxAge: \"0d\"` effectively disables caching.\n\n---\n\n## 4. SwPush registration\n\n```ts\n// services/push.service.ts\nimport { Injectable } from \"@angular/core\";\nimport { SwPush } from \"@angular/service-worker\";\nimport { Router } from \"@angular/router\";\nimport { environment } from \"../../environments/environment\";\n\n@Injectable({ providedIn: \"root\" })\nexport class PushService {\n  constructor(private swPush: SwPush, private router: Router) {\n    // Listen for notification clicks — fired when user taps a push notification\n    this.swPush.notificationClicks.subscribe(({ notification }) => {\n      const data = notification.data;\n      const targetUrl = data.receiverType === \"group\"\n        ? `/messages/group/${data.conversationId}`\n        : `/messages/user/${data.senderUid}`;\n      this.router.navigateByUrl(targetUrl);\n    });\n  }\n\n  async subscribe(uid: string): Promise<void> {\n    if (!this.swPush.isEnabled) {\n      console.warn(\"SwPush not enabled — Service Worker not registered or not supported\");\n      return;\n    }\n\n    try {\n      const subscription = await this.swPush.requestSubscription({\n        serverPublicKey: environment.vapidPublic,\n      });\n\n      // Send to YOUR push server, keyed by uid\n      await fetch(\"/api/push/subscribe\", {\n        method: \"POST\",\n        headers: { \"Content-Type\": \"application/json\" },\n        body: JSON.stringify({ uid, subscription }),\n      });\n    } catch (err) {\n      // User denied permission, or push not supported\n      console.warn(\"Web Push subscription failed:\", err);\n    }\n  }\n\n  async unsubscribe(uid: string): Promise<void> {\n    if (!this.swPush.isEnabled) return;\n    const subscription = await this.swPush.subscription.toPromise();\n    if (!subscription) return;\n\n    await fetch(\"/api/push/unsubscribe\", {\n      method: \"POST\",\n      headers: { \"Content-Type\": \"application/json\" },\n      body: JSON.stringify({ uid, subscription }),\n    });\n    await this.swPush.unsubscribe();\n  }\n}\n```\n\n**Permission gesture rule:** `requestSubscription` triggers the browser's permission prompt. Browsers require this in response to a user gesture (click). Don't call this from `OnInit` — wire it to a button click.\n\n---\n\n## 5. Wire into the auth flow\n\n```ts\n// services/auth.service.ts (or wherever login happens)\nimport { PushService } from \"./push.service\";\n\n@Injectable({ providedIn: \"root\" })\nexport class AuthService {\n  constructor(private pushService: PushService) {}\n\n  async loginAndEnableChat(uid: string): Promise<void> {\n    await CometChatUIKit.login(uid);\n    // Don't auto-subscribe to push — let the user opt in via a button\n  }\n\n  async logout(uid: string): Promise<void> {\n    await this.pushService.unsubscribe(uid);     // CRITICAL — clean up before SDK logout\n    await CometChatUIKit.logout();\n  }\n}\n```\n\n---\n\n## 6. \"Enable notifications\" button\n\n```ts\n// components/notification-prompt.component.ts\n@Component({\n  selector: \"app-notification-prompt\",\n  template: `\n    <button (click)=\"enable()\" *ngIf=\"!enabled\">\n      Enable notifications\n    </button>\n  `,\n})\nexport class NotificationPromptComponent {\n  enabled = false;\n\n  constructor(private pushService: PushService, private auth: AuthService) {}\n\n  async enable() {\n    const user = await CometChatUIKit.getLoggedInUser();\n    if (!user) return;\n    await this.pushService.subscribe(user.uid);\n    this.enabled = true;\n  }\n}\n```\n\nMount this somewhere visible — chat header, settings page, or a one-time onboarding card.\n\n---\n\n## 7. Service Worker push event handler\n\n`@angular/service-worker` ships its own `ngsw-worker.js` that handles push events automatically — when a push arrives, it calls `ServiceWorkerRegistration.showNotification` with the push payload.\n\n**However**, the default behavior shows the entire payload as the notification body. For chat, you want a custom UI: sender name as title, preview as body, conversation tag for dedup.\n\nTwo paths:\n\n### Path A — Use the payload shape `ngsw-worker.js` expects\n\nThe Angular SW expects this payload shape:\n\n```json\n{\n  \"notification\": {\n    \"title\": \"Alice\",\n    \"body\": \"Hi there\",\n    \"icon\": \"/avatars/alice.png\",\n    \"tag\": \"chat-conversation-123\",\n    \"data\": {\n      \"conversationId\": \"conversation-123\",\n      \"senderUid\": \"cometchat-uid-1\",\n      \"receiverType\": \"user\"\n    }\n  }\n}\n```\n\nYour push server sends this exact shape. `ngsw-worker.js` extracts `notification` and shows it.\n\n### Path B — Custom Service Worker\n\nIf you need behavior beyond what Angular's SW provides (e.g. checking if the chat tab is focused before notifying), eject from `ngsw-worker.js` and write a custom SW. Heavier maintenance — only needed for advanced cases.\n\n---\n\n## 8. Server-side push send\n\nSame shape as `cometchat-react-push` Section 6. Different payload shape — Angular SW expects the wrapped `{ notification: {...} }` form:\n\n```ts\nconst payload = JSON.stringify({\n  notification: {\n    title: sender.name,\n    body: truncate(data.text, 80),\n    icon: sender.avatar ?? \"/icons/chat.png\",\n    tag: `chat-${receiver}`,\n    data: {\n      conversationId: receiver,\n      senderUid: sender.uid,\n      receiverType: data.entityType,\n    },\n  },\n});\n```\n\nThe webhook setup, signature verification, and dead-subscription cleanup are identical to the React version.\n\n---\n\n## 9. CometChat webhook setup\n\nSame as `cometchat-react-push` Section 7 — configure in CometChat dashboard, point at your push server, copy the signing secret.\n\n---\n\n## 10. Angular Universal (SSR) considerations\n\nIf your app uses Angular Universal:\n\n```ts\n// services/push.service.ts\nimport { isPlatformBrowser } from \"@angular/common\";\nimport { Inject, PLATFORM_ID } from \"@angular/core\";\n\n@Injectable({ providedIn: \"root\" })\nexport class PushService {\n  constructor(\n    private swPush: SwPush,\n    private router: Router,\n    @Inject(PLATFORM_ID) private platformId: object,\n  ) {\n    if (!isPlatformBrowser(this.platformId)) return;        // skip on server\n    this.swPush.notificationClicks.subscribe(/* ... */);\n  }\n\n  async subscribe(uid: string): Promise<void> {\n    if (!isPlatformBrowser(this.platformId)) return;\n    if (!this.swPush.isEnabled) return;\n    // ...\n  }\n}\n```\n\nWithout these guards, the SSR build crashes at boot with `ReferenceError: ServiceWorkerRegistration is not defined`.\n\n`@angular/service-worker` itself ships with SSR-safe stubs, but downstream code that touches `swPush.subscription` etc. needs guards.\n\n---\n\n## 11. iOS Safari 16.4+ PWA-only caveat\n\nSame as `cometchat-react-push` Section 9 — iOS Web Push works ONLY for sites added to the Home Screen as PWAs. Angular CLI generates `manifest.webmanifest` automatically when you `ng add @angular/pwa`; verify it's present and correct:\n\n```json\n{\n  \"name\": \"Your App\",\n  \"short_name\": \"YourApp\",\n  \"theme_color\": \"#1976d2\",\n  \"background_color\": \"#fafafa\",\n  \"display\": \"standalone\",\n  \"start_url\": \"/\",\n  \"icons\": [/* ... */]\n}\n```\n\niOS users must use Safari → Share → \"Add to Home Screen\" once for Web Push to work.\n\n---\n\n## 12. HTTPS requirement\n\nService Workers + Push API both require HTTPS (or `localhost`). `ng serve --ssl` works for local HTTPS dev.\n\nFor production: Vercel / Netlify / Cloudflare Pages / Firebase Hosting all default to HTTPS.\n\n---\n\n## 13. Build + deploy\n\n`@angular/service-worker` generates `ngsw-worker.js` at build time:\n\n```bash\nng build --configuration production\n```\n\nOutput: `dist/your-app/ngsw-worker.js` + `dist/your-app/ngsw.json` (the SW config baked in).\n\nDeploy these files alongside `index.html`. The SW updates automatically when `ngsw.json` changes (a hash of all included assets); users get the new SW on next page load.\n\n---\n\n## 14. Anti-patterns\n\n1. **Calling `swPush.requestSubscription()` from `ngOnInit`.** Browsers reject permission requests not tied to user gestures. Wire to a button click.\n2. **Subscribing on `OnInit` of a lazy module.** Lazy modules instantiate after navigation; user has already missed pushes during navigation. Subscribe in eager AppModule.\n3. **Sending the Auth Key in push payloads.** Visible in the Service Worker. Use the user's UID as a key into your server.\n4. **Skipping the SSR guard** in Angular Universal projects. SSR boot crashes.\n5. **Not unsubscribing on logout.** Previous user keeps getting notifications for the new user's messages.\n6. **Hardcoding the VAPID public key in `environment.ts`.** It's not secret (it's \"public\"), but rotate-able is better — read from runtime config.\n7. **Using `*ngIf=\"enabled\"` based on a local boolean** instead of `swPush.subscription` observable. Multi-tab scenarios get out of sync.\n\n---\n\n## 15. Verification checklist\n\n- [ ] `@angular/service-worker` installed via `ng add`\n- [ ] `ngsw-config.json` exists and excludes CometChat API from caching\n- [ ] `ServiceWorkerModule.register('ngsw-worker.js', { enabled: !isDevMode() })` in AppModule\n- [ ] `PushService` injects `SwPush`\n- [ ] `swPush.notificationClicks.subscribe` routes to the conversation\n- [ ] VAPID public key in `environment.ts`; private key in server env (NOT client)\n- [ ] Subscribe via a user-clicked button, not OnInit\n- [ ] Subscription registered AFTER login resolves\n- [ ] Logout unsubscribes BEFORE `CometChatUIKit.logout()`\n- [ ] Server sends `{ notification: {...} }` payload shape (Angular SW format)\n- [ ] CometChat dashboard webhook configured + signature verified\n- [ ] HTTPS or localhost only\n- [ ] `manifest.webmanifest` present for iOS PWA support\n- [ ] Angular Universal: `isPlatformBrowser` guards in PushService\n\n---\n\n## 16. Pointers\n\n- `cometchat-react-push` — sister skill; covers VAPID, server-side webhook, iOS PWA caveat in depth\n- `cometchat-angular-core` — login order\n- `cometchat-angular-patterns` — module setup, lazy loading\n- `cometchat-angular-troubleshooting` — SW registration debugging, ngsw issues","tags":["cometchat","angular","push","skills","agent-skills","ai-agent","chat","claude-code","cursor","messaging","nextjs","react"],"capabilities":["skill","source-cometchat","skill-cometchat-angular-push","topic-agent-skills","topic-ai-agent","topic-chat","topic-claude-code","topic-cometchat","topic-cursor","topic-messaging","topic-nextjs","topic-react","topic-react-native","topic-ui-kit"],"categories":["cometchat-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/cometchat/cometchat-skills/cometchat-angular-push","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add cometchat/cometchat-skills","source_repo":"https://github.com/cometchat/cometchat-skills","install_from":"skills.sh"}},"qualityScore":"0.463","qualityRationale":"deterministic score 0.46 from registry signals: · indexed on github topic:agent-skills · 27 github stars · SKILL.md body (12,474 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T19:04:48.580Z","embedding":null,"createdAt":"2026-05-18T07:04:20.429Z","updatedAt":"2026-05-18T19:04:48.580Z","lastSeenAt":"2026-05-18T19:04:48.580Z","tsv":"'-123':744 '-15':16 '/**':313 '/../environments/environment':361 '/api/push/subscribe':437 '/api/push/unsubscribe':481 '/api/service-worker/swpush':141 '/assets':290 '/avatars/alice.png':735 '/doc/html/rfc8030':147 '/favicon.ico':277 '/guide/service-worker-intro':136 '/icons/chat.png':843 '/index.html':269,278 '/manifest.webmanifest':279 '/messages/group':395 '/messages/user':397 '/node_modules':266 '/push.service':542 '0d':318,337 '1':148,749,1154 '10':895 '100':316 '11':989 '12':15,1069 '123':740 '13':1101 '14':1150 '15':1295 '16':1385 '16.4':992 '1976d2':1044 '2':180,1173 '3':233,1197 '30000':229 '4':341,1221 '5':527,1233 '6':592,819,1249 '7':653,881,1274 '8':805 '80':840 '9':870,1004 'abl':1267 'ad':1012 'add':181,185,188,1027,1059,1302 'advanc':803 'alic':730 'alongsid':1126 'alreadi':1188 'angular':3,9,14,42,44,50,53,69,94,101,166,721,776,823,896,904,1019,1227,1360,1379,1406,1412,1420 'angular.io':135,140 'angular.io/api/service-worker/swpush':139 'angular.io/guide/service-worker-intro':134 'angular.json':206 'angular/common':911 'angular/core':349,917 'angular/pwa':1028 'angular/router':357 'angular/service-worker':25,55,133,182,186,189,353,659,972,1104,1298 'angular/service-worker/config/schema.json':267 'anti':1152 'anti-pattern':1151 'api':65,138,262,308,310,325,1075,1308 'apng':296 'app':272,601,902,1038 'app-notification-prompt':600 'app.module.ts':211 'application/json':444,488 'appmodul':201,232,1196,1316 'architectur':149 'arriv':672 'asset':244,283,1140 'assetgroup':270 'async':401,464,553,576,624,945 'auth':531,622,1200 'authservic':548,623 'auto':564 'auto-subscrib':563 'automat':668,1023,1131 'avif':298 'await':423,435,474,479,493,558,581,590,628,633 'b':766 'background':1045 'bake':1121 'base':1278 'bash':183,1110 'behavior':683,773 'better':1269 'beyond':774 'bodi':445,489,691,705,731,837 'boolean':1282 'boot':965,1231 'browser':501,505,1159 'browsermodul':215 'build':207,962,1102,1108,1112 'button':525,575,595,605,1171,1343 'cach':243,323,340,1310 'cacheconfig':314 'call':263,517,674,1155 'card':652 'case':804 'catch':449 'caveat':126,996,1401 'chang':1134 'chat':238,251,642,693,738,784,845 'chat-convers':737 'check':781 'checklist':1297 'class':231,366,547,613,922 'clean':585 'cleanup':863 'cli':1020 'click':38,378,514,526,606,1172,1342 'click-through':37 'client':160,172,1336 'client-sid':171 'cloudflar':1093 'code':982 'color':1043,1046 'cometchat':2,8,51,93,100,106,157,164,307,324,747,815,871,877,884,1000,1307,1363,1388,1405,1411,1419 'cometchat-angular-cor':92,1404 'cometchat-angular-pattern':99,1410 'cometchat-angular-push':1 'cometchat-angular-troubleshoot':1418 'cometchat-api':306 'cometchat-react-push':105,156,814,876,999,1387 'cometchat-uid':746 'cometchat.io':312 'cometchat.io/**':311 'cometchatuikit.getloggedinuser':629 'cometchatuikit.login':559 'cometchatuikit.logout':591,1354 'compon':598 'components/notification-prompt.component.ts':597 'concept':118 'config':236,242,1120,1273 'configur':882,1113,1366 'consider':899 'console.warn':408,458 'const':388,391,421,472,626,831 'constructor':368,549,617,924 'content':442,486 'content-typ':441,485 'convers':706,739,743,1324 'conversationid':742,848 'copi':891 'core':95,1407 'correct':1034 'cover':24,127,1393 'crash':963,1232 'creat':192 'critic':584 'css':280 'cur':292 'custom':697,767,796 'dashboard':885,1364 'data':258,389,741,847 'data-sourc':257 'data.conversationid':396 'data.entitytype':853 'data.receivertype':393 'data.senderuid':398 'data.text':839 'datagroup':304 'datatracker.ietf.org':146 'datatracker.ietf.org/doc/html/rfc8030':145 'dead':861 'dead-subscript':860 'debug':1424 'dedup':709 'default':226,241,682,1098 'defin':971 'deni':452 'depend':191 'deploy':1103,1123 'depth':130,1403 'dev':223,1088 'di':71 'differ':820 'disabl':221,339 'display':1048 'dist/your-app/ngsw-worker.js':1116 'dist/your-app/ngsw.json':1117 'doesn':19 'downstream':981 'e.g':780 'eager':1195 'effect':338 'eject':790 'els':176 'enabl':218,411,593,607,609,610,615,625,1277,1313 'entir':686 'env':1334 'environ':359 'environment.ts':1256,1329 'environment.vapidpublic':426 'err':450,463 'etc':986 'event':657,667 'everyth':175 'exact':757 'exclud':1306 'exist':1304 'expect':719,723,825 'explan':116 'export':230,365,546,612,921 'extract':760 'fafafa':1047 'fail':462 'fals':616 'fetch':436,480 'file':276,289,1125 'fire':379 'firebas':1095 'first':91,248 'flavor':115 'flow':532 'focus':787 'form':829 'format':1362 'fresh':320,333 'generat':1021,1105 'gestur':496,513,1167 'get':1142,1241,1291 'gif':299 'ground':131 'group':394 'guard':959,988,1225,1382 'hand':83 'hand-rol':82 'handl':665 'handler':658 'happen':538 'hardcod':1250 'hash':1136 'header':440,484,643 'heavier':798 'hi':732 'home':1015,1061 'host':1096 'howev':680 'https':1070,1078,1087,1100,1369 'icon':734,841,1052 'id':915,933 'ident':865 'import':197,214,346,350,354,358,539,908,912 'includ':1139 'index':268 'index.html':1127 'inject':347,362,543,913,918,931,1318 'instal':1299 'installmod':273,284 'instanti':1183 'instead':1283 'integr':52,67 'io':124,990,1005,1053,1376,1399 'isdevmod':219,1314 'isplatformbrows':909,938,951,1381 'issu':1426 'jpeg':294 'jpg':293 'js':281 'json':264,727,1035 'json.stringify':446,490,833 'keep':1240 'key':432,1201,1217,1254,1327,1331 'kit':11 'lazi':285,1179,1181,1416 'let':568 'listen':375 'live':261 'load':1149,1417 'local':1086,1281 'localhost':1080,1371 'login':97,537,1349,1408 'loginandenablechat':554 'logout':577,589,1237,1351 'mainten':799 'manifest.webmanifest':1022,1373 'maxag':317,336 'maxsiz':315 'messag':1248 'method':438,482 'minim':235 'miss':1189 'mode':224 'modul':103,1180,1182,1414 'most':254 'mount':638 'multi':1288 'multi-tab':1287 'must':1055 'name':271,282,305,700,1036,1040 'nativ':22 'navig':1185,1192 'need':255,772,801,987 'netlifi':1092 'new':1144,1245 'next':1147 'ng':184,1026,1081,1111,1301 'ngif':608,1276 'ngmodul':213 'ngoninit':1158 'ngsw':1425 'ngsw-config.json':27,193,234,1303 'ngsw-worker.js':199,217,663,718,759,792,1106,1312 'ngsw.json':1133 'notif':6,377,385,387,594,602,611,690,728,761,828,834,1242,1357 'notifi':789 'notification.data':390 'notificationpromptcompon':614 'object':936 'observ':1286 'offlin':247 'offline-first':246 'onboard':651 'one':649 'one-tim':648 'oninit':520,1176,1345 'opt':571 'option':208 'order':98,1409 'otf':300 'output':1115 'packag':56 'page':645,1094,1148 'path':80,711,712,765 'pattern':102,1153,1413 'payload':679,687,716,725,821,832,1204,1358 'permiss':453,495,503,1161 'platform':914,932 'platformid':935 'png':295 'point':886 'pointer':1386 'post':439,483 'prefetch':274,287 'present':1032,1374 'preview':703 'previous':1238 'primit':174 'privat':369,372,550,618,621,925,928,934,1330 'product':1090,1114 'project':17,195,1229 'promis':405,468,557,580,949 'prompt':504,603 'provid':57,779 'providedin':363,544,919 'public':1253,1263,1326 'purpos':46 'push':4,5,23,36,48,64,108,111,143,159,162,239,252,384,430,455,460,567,656,666,671,678,753,809,817,879,889,1002,1007,1066,1074,1190,1203,1390 'pushservic':367,540,551,552,619,620,923,1317,1384 'pwa':125,994,1377,1400 'pwa-on':993 'pwas':249,1018 're':328 'react':107,114,158,816,868,878,1001,1389 'react-flavor':113 'read':87,1270 'real':330 'real-tim':329 'receiv':846,849 'receivertyp':750,852 'recommend':79 'referenceerror':967 'regist':415,1347 'registerwhenst':228 'registr':343,1423 'registrationstrategi':227 'reject':1160 'request':1162 'requestsubscript':498 'requir':506,1071,1077 'resolv':1350 'resourc':275,288 'respons':326,509 'return':419,471,478,632,940,953,956 'roll':84 'root':196,364,545,920 'rotat':1266 'rotate-':1265 'rout':40,1321 'router':43,74,355,373,374,929,930 'rule':497 'runtim':1272 'safari':991,1057 'safe':978 'scenario':1290 'schema':265 'screen':1016,1062 'sdk':588 'secret':894,1260 'section':818,880,1003 'selector':599 'send':35,427,755,810,1198,1356 'sender':699 'sender.avatar':842 'sender.name':836 'sender.uid':851 'senderuid':745,850 'serv':1082 'server':31,121,163,431,754,807,890,943,1220,1333,1355,1396 'server-sid':30,120,806,1395 'serverpublickey':425 'servic':85,412,654,768,1072,1208 'services/auth.service.ts':534 'services/push.service.ts':345,907 'servicework':203 'serviceworkermodule.register':198,216,1311 'serviceworkerregistr':968 'serviceworkerregistration.shownotification':675 'set':202,644 'setup':26,104,856,873,1415 'shape':154,717,726,758,812,822,1359 'share':117,1058 'ship':660,974 'short':1039 'show':684,763 'side':32,122,173,808,1397 'sign':893 'signatur':857,1367 'sister':1391 'site':1011 'skill':90,1392 'skill-cometchat-angular-push' 'skip':941,1222 'somewher':640 'sourc':259 'source-cometchat' 'spec':112,144 'ssl':1083 'ssr':898,961,977,1224,1230 'ssr-safe':976 'standalon':1049 'start':1050 'strategi':319,334 'string':404,467,556,579,948 'stub':979 'subscrib':402,565,946,1174,1193,1337 'subscript':29,422,448,461,473,477,492,862,1346 'support':418,457,1378 'svg':291 'sw':161,220,722,778,797,824,1119,1129,1145,1361,1422 'swpush':28,58,137,168,342,351,370,371,409,926,927,1319 'swpush.notificationclicks.subscribe':1320 'swpush.requestsubscription':1156 'swpush.subscription':985,1285 'sync':1294 'tab':785,1289 'tag':707,736,844 'tap':382 'targeturl':392,400 'templat':604 'theme':1042 'thin':60 'this.enabled':636 'this.platformid':939,952 'this.pushservice.subscribe':634 'this.pushservice.unsubscribe':582 'this.router.navigatebyurl':399 'this.swpush.isenabled':407,470,955 'this.swpush.notificationclicks.subscribe':386,944 'this.swpush.requestsubscription':424 'this.swpush.subscription.topromise':475 'this.swpush.unsubscribe':494 'tie':1164 'time':331,650,1109 'titl':702,729,835 'topic-agent-skills' 'topic-ai-agent' 'topic-chat' 'topic-claude-code' 'topic-cometchat' 'topic-cursor' 'topic-messaging' 'topic-nextjs' 'topic-react' 'topic-react-native' 'topic-ui-kit' 'touch':984 'tree':72 'tri':420 'trigger':499 'troubleshoot':1421 'true':204,637 'truncat':838 'truth':132 'ts':212,344,533,596,830,906 'ttf':301 'two':710 'type':443,487 'ui':10,698 'uid':403,434,447,466,491,555,560,578,583,748,947,1214 'uikitsettingsbuild':96 'univers':897,905,1228,1380 'universa':45 'unsubscrib':465,1235,1352 'updat':1130 'updatemod':286 'url':309,1051 'use':714,903,1056,1210,1275 'user':381,451,512,570,627,631,751,1054,1141,1166,1186,1212,1239,1246,1341 'user-click':1340 'user.uid':635 'v4':12 'vapid':119,1252,1325,1394 'vercel':1091 'verif':858,1296 'verifi':209,1029,1368 'version':869 'via':41,573,1300,1338 'visibl':641,1205 'want':695 'web':18,47,110,142,152,459,1006,1065 'webhook':33,123,165,855,872,1365,1398 'webp':297 'wherev':536 'wire':521,528,1168 'without':957 'woff':302 'woff2':303 'work':1008,1068,1084 'worker':86,413,655,769,1073,1209 'wrap':827 'wrapper':61 'write':794 'yourapp':1041","prices":[{"id":"32b5000e-03f3-4fb2-a60e-7d7d7413b123","listingId":"0b6dab9b-5f28-4559-b757-4f07fcb0d6dc","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"cometchat","category":"cometchat-skills","install_from":"skills.sh"},"createdAt":"2026-05-18T07:04:20.429Z"}],"sources":[{"listingId":"0b6dab9b-5f28-4559-b757-4f07fcb0d6dc","source":"github","sourceId":"cometchat/cometchat-skills/cometchat-angular-push","sourceUrl":"https://github.com/cometchat/cometchat-skills/tree/main/skills/cometchat-angular-push","isPrimary":false,"firstSeenAt":"2026-05-18T07:04:20.429Z","lastSeenAt":"2026-05-18T19:04:48.580Z"}],"details":{"listingId":"0b6dab9b-5f28-4559-b757-4f07fcb0d6dc","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"cometchat","slug":"cometchat-angular-push","github":{"repo":"cometchat/cometchat-skills","stars":27,"topics":["agent-skills","ai-agent","chat","claude-code","cometchat","cursor","messaging","nextjs","react","react-native","ui-kit"],"license":null,"html_url":"https://github.com/cometchat/cometchat-skills","pushed_at":"2026-05-18T05:04:24Z","description":"Add CometChat chat to any React, Next.js, React Native, Angular, Android, iOS, or Flutter project through your AI coding agent. Works with Claude Code, Cursor, Codex, VS Code Copilot, Windsurf, Cline, Kiro, and 50+ more agents.","skill_md_sha":"160d2a3bd603ea0c45542d8f1a9913187a35288f","skill_md_path":"skills/cometchat-angular-push/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/cometchat/cometchat-skills/tree/main/skills/cometchat-angular-push"},"layout":"multi","source":"github","category":"cometchat-skills","frontmatter":{"name":"cometchat-angular-push","license":"MIT","description":"Push notifications for CometChat Angular UI Kit v4 in Angular 12-15 projects. Web doesn't have native push — covers @angular/service-worker setup, ngsw-config.json, SwPush subscription, server-side webhook to send pushes, click-through routing via Angular Router, Angular Universal SSR considerations, and HTTPS requirements.","compatibility":"Angular 12-15 (LTS focus on 15); @angular/service-worker (matched to Angular major); Web Push API (Chrome 50+, Firefox 44+, Edge 17+, Safari 16+ desktop, Safari 16.4+ iOS PWA-only); HTTPS required"},"skills_sh_url":"https://skills.sh/cometchat/cometchat-skills/cometchat-angular-push"},"updatedAt":"2026-05-18T19:04:48.580Z"}}