Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion references/platform-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ What the agent can do vs. what stays human, per platform. Verified against SDK r
|---|---|---|---|
| **Web** (most automatable) | `<script src="https://cdn.onesignal.com/sdks/web/v16/OneSignalSDK.page.js" defer>` + `OneSignal.init({appId})` via `OneSignalDeferred`; create `OneSignalSDKWorker.js` containing one line `importScripts("https://cdn.onesignal.com/sdks/web/v16/OneSignalSDK.sw.js");` placed in `public/` (maps to origin root on Next/CRA/Vite/Vue/Angular) | Dashboard web-platform config (Site URL must EXACTLY match origin); confirm deployed site serves the worker same-origin over HTTPS with `Content-Type: application/javascript` | **Signup/onboarding does NOT provision the web platform** — until the dashboard step happens the SDK fails init with `App not configured for web push` (sync `code: 2`); probe `GET /sync/{app_id}/web` (api-reference), or provision via the write-once credentials endpoint (`chrome_web_origin`). SW must be same-origin — no CDN/subdomain. Subdir placement needs `serviceWorkerPath` + `serviceWorkerParam.scope`. PWA conflict: only one SW per scope. localhost testing works with a separate app + `allowLocalhostAsSecureOrigin: true`. Frameworks: react-onesignal / onesignal-vue3 / onesignal-ngx npm packages. |
| **Expo** (best mobile) | `npx expo install onesignal-expo-plugin` + `npm i react-native-onesignal`; app.json: plugin FIRST in plugins array, `mode`, `smallIcons`; init in `App.tsx`/`_layout.tsx` | Apple .p8 + Firebase JSON procurement; EAS credentials must match; dev build (push doesn't work in Expo Go) | Config plugin generates NSE, entitlements, App Group, UIBackgroundModes at prebuild — zero manual Xcode. Expo SDK 53+/RN 0.79+ New Architecture. |
| **Android** | Gradle dep `com.onesignal:OneSignal:<exact Stable from releases.json>` (e.g. `5.9.1` — **exact pin, never a version range**); Application subclass with `OneSignal.initWithContext(this, APP_ID)`; register in AndroidManifest `android:name`; `requestPermission` call | Firebase console: create project (if none), generate **service-account JSON**; (agent uploads it via API) | **`google-services.json` is NOT required for OneSignal** — FCM v1 credentials are server-side only (the upstream ai-prompt requiring it is a known bug; only keep it if the app itself uses Firebase client SDKs). `POST_NOTIFICATIONS` is manifest-merged by the SDK — no edit. Play-services emulator OK for testing. **Version + Kotlin floor:** resolve the exact pin with `python3 scripts/resolve_sdk_version.py android` (never compose the line by hand — ranges break the build). The SDK's OpenTelemetry submodule transitively pulls a newer `kotlin-stdlib` than its POM admits (5.9.1 resolves to 2.2.20), so the host needs a Kotlin compiler that can read that metadata — check it with `python3 scripts/android_kotlin_check.py . --deps -` (fed a `:app:dependencies` run) and surface any required toolchain bump as a separately-approved change; never half-bump to a version still below the resolved stdlib. |
| **Android** | Gradle dep `com.onesignal:OneSignal:<exact Stable from releases.json>` (e.g. `5.9.1` — **exact pin, never a version range**); Application subclass with `OneSignal.initWithContext(this, APP_ID)`; register in AndroidManifest `android:name`; `requestPermission` call | Firebase console: create project (if none), generate **service-account JSON**; (agent uploads it via API) | **`google-services.json` is not required for OneSignal** — FCM v1 credentials are server-side only — but it is **recommended on SDK 5.11.0+**, and **required** when `firebase_messaging_installation_id_enabled` is `true` in the merged manifest. The agent never adds or removes it (the user supplies it from the Firebase console); never apply the Gradle plugin without the file, which fails the build. `POST_NOTIFICATIONS` is manifest-merged by the SDK — no edit. Play-services emulator OK for testing. **Version + Kotlin floor:** resolve the exact pin with `python3 scripts/resolve_sdk_version.py android` (never compose the line by hand — ranges break the build). The SDK's OpenTelemetry submodule transitively pulls a newer `kotlin-stdlib` than its POM admits (5.9.1 resolves to 2.2.20), so the host needs a Kotlin compiler that can read that metadata — check it with `python3 scripts/android_kotlin_check.py . --deps -` (fed a `:app:dependencies` run) and surface any required toolchain bump as a separately-approved change; never half-bump to a version still below the resolved stdlib. |
| **iOS native** | SPM (`OneSignal-XCFramework` repo, map `OneSignalFramework`→app) or Podfile; `OneSignal.initialize(appId, withLaunchOptions:)` in AppDelegate/adaptor; Info.plist `UIBackgroundModes remote-notification`; entitlements text; debug-only verification helper | Apple Developer portal: paid account, Push capability on App ID, generate **.p8** (+ Key ID/Team ID); Xcode GUI: signing, capabilities toggle, and **NSE target creation** (File>New>Target — not reliably text-editable); physical-device test (launch from Xcode) + the notification permission tap | NSE is OPTIONAL for minimal install (needed only for rich media / confirmed receipt / badges) — OneSignal's own AI prompt skips it. Never hardcode a fallback App ID. Remote push testing: physical device (human launches), or a simulator on an Apple-silicon Mac that the verify skill boots, installs, and launches itself via `simctl` (Xcode 14+ simulators there receive real sandbox APNs pushes; Intel-Mac simulators do not) — only the permission tap stays human. |
| **React Native / Flutter / Cordova / Capacitor** | Package install (`react-native-onesignal` / `onesignal_flutter` / `onesignal-cordova-plugin` / `@onesignal/capacitor-plugin` + `npx cap sync` — exact Stable version from releases.json, no ranges/carets); init call in entry file; Podfile NSE pod line; version bumps (RN 0.79+ New Arch, Flutter 3.29+) | Everything in the iOS-native human column (these wrappers inherit the full native iOS GUI flow); Firebase JSON procurement | Capacitor: set `ios.handleApplicationNotifications: false` in capacitor.config. Android side is handled by the wrapper plugins — no manual gradle/manifest edits documented. |
| **Unity** | Init `OneSignal.Initialize("APP_ID")` in a MonoBehaviour; icon assets | GUI-bound install (Package Manager/Asset Store), Player Settings gradle-template toggles | Low human friction but low agent-automatability. Unity 2022.3+, Android API 33+. |
Expand Down
19 changes: 12 additions & 7 deletions scripts/verify_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,13 +252,18 @@ def android_manifest_no_node_replace(self):
),
)

def android_no_stray_google_services(self):
def android_google_services_plugin_without_file(self):
"""The Gradle plugin without google-services.json fails the build.

The file alone is fine, and is recommended on SDK 5.11.0+, so its mere
presence is not a finding. Applying the plugin with no file to process
is, because the build fails with "File google-services.json is missing".
"""
gs = [fp for fp in walk_files(self.root) if os.path.basename(fp) == "google-services.json"]
firebase = grep(self.root, r"com\.google\.firebase")
# only a problem if google-services.json exists AND the app doesn't otherwise use Firebase
bad = bool(gs) and not firebase
self.add("android_no_stray_google_services", not bad, "warn",
"" if not bad else "google-services.json present but no Firebase client usage — OneSignal does NOT require it")
plugin = grep(self.root, r"com\.google\.gms(\.|:)google-services")
bad = bool(plugin) and not gs
self.add("android_google_services_plugin_without_file", not bad, "error",
"" if not bad else "com.google.gms.google-services is applied but no google-services.json was found \u2014 the build will fail. Add the file from the Firebase console, or remove the plugin.")

def android_requestpermission_not_callback(self):
# OneSignal.Notifications.requestPermission is `suspend fun(Boolean): Boolean`
Expand Down Expand Up @@ -589,7 +594,7 @@ def run(self):
by_platform = {
"android": [self.android_init_in_application, self.android_manifest_registers_app,
self.android_manifest_no_node_replace,
self.android_no_stray_google_services, self.android_verification_debug_guarded,
self.android_google_services_plugin_without_file, self.android_verification_debug_guarded,
self.android_requestpermission_not_callback, self.android_buildconfig_feature_enabled,
self.android_coroutines_on_classpath],
# Unity ships via .unitypackage/UPM (a GUI step, no text manifest we can
Expand Down
2 changes: 1 addition & 1 deletion skills/credentials/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ Full portal detail (enable-FCM-v1 detour, required service-account permissions,
3. **Generate the key:** Project settings → **Service accounts** → **Generate new private key** → confirm → a `.json` downloads. This file is a secret.
4. **Human tells you the file path** to the downloaded JSON.
5. **You (agent):** confirm the JSON is gitignored / outside the repo, ask the consent question ([The API-upload mechanism](#the-api-upload-mechanism-shared-by-apple-p8-and-firebase)), then upload: the MCP tool with the Base64 string as **`fcm_v1_service_account_json`** (the only required Android param per the reference page), or `onesignal_api.py provision <APP_ID> --fcm-json <path>` on the direct path. Validate the response.
6. **`google-services.json` is NOT a OneSignal credential.** OneSignal authenticates to FCM entirely server-side with the service-account JSON. Do not ask for `google-services.json` and do not upload it. It is only relevant if the app *itself* uses Firebase client SDKs — that's the app's own concern, not OneSignal's. (The upstream ai-prompt that requires it is a known bug; see platform-matrix Android notes.)
6. **`google-services.json` is not a OneSignal credential.** OneSignal authenticates to FCM entirely server-side with the service-account JSON. Never ask the user to upload `google-services.json` to OneSignal, and never upload it yourself. It is Firebase *client* configuration that ships inside the app — recommended on Android SDK 5.11.0+, and set up in the **setup** skill, not here. Both files must come from the same Firebase project; a mismatch produces subscription status `-6`. See platform-matrix Android notes.

## Credential validation loop

Expand Down
2 changes: 1 addition & 1 deletion skills/credentials/api-uploaded-credentials.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ OneSignal authenticates to FCM entirely server-side using the service-account JS
- Do not upload it.
- It only matters if the app *itself* embeds Firebase client SDKs (Analytics, Firestore, etc.) — that's the app's own setup, unrelated to OneSignal push.

The upstream OneSignal ai-install-prompt that asks for `google-services.json` is a **known bug** (see [../../references/platform-matrix.md](../../references/platform-matrix.md) Android notes). Follow this file, not that prompt.
`google-services.json` is never uploaded to OneSignal — it is client configuration handled by the **setup** skill, not a credential (see [../../references/platform-matrix.md](../../references/platform-matrix.md) Android notes). It must come from the same Firebase project as the service account uploaded here, or Android subscriptions show status `-6`.

### FCM failure modes → what to tell the user (surface the raw error first)
| Symptom | Likely cause | Fix |
Expand Down
2 changes: 1 addition & 1 deletion skills/setup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ Decide what is still missing:

Emit a copy-ready summary: files changed; SDK version + that it came from the releases.json endpoint; the dashboard/console steps the human still owns (from the platform's "Human must do" column in the matrix); verification steps (run the debug build → accept the permission prompt → the verify skill confirms the subscription and sends the test push); the verification helper's filename + call site (debug-only; safe to keep, deletable on request); and rollback commands (`git checkout -- <files>` / delete the `onesignal-integration` branch / restore `.onesignal.bak` files). **Do NOT auto-commit or open a PR** — offer the commands; the user runs them.

Before finishing, **run the structural self-check and fix anything it flags** — do not rely on the build or on your own reading: `python3 <plugin>/scripts/verify_integration.py <project_dir> --platform <platform> --app-id <APP_ID>`. It deterministically verifies the constraint-following facts a compiler cannot see (exact version pin, no placeholder/fabricated App ID, init in an Application subclass, the verification file guarded by `BuildConfig.DEBUG` so it can't ship to release, `onesignal:managed` markers, no stray `google-services.json`, no deprecated `addOutcome`). If `verdict` is `fail`, repair each error-level check and re-run until it passes; only then declare done. This is the deterministic close of the loop — the agent catches its own slips (e.g. a verification file that names `installIfDebug` but forgets the guard) instead of shipping them. If the script cannot run because Python is missing (Step 0.2), check each item in that list by reading the files you wrote, and say in the summary that the structural self-check did not run.
Before finishing, **run the structural self-check and fix anything it flags** — do not rely on the build or on your own reading: `python3 <plugin>/scripts/verify_integration.py <project_dir> --platform <platform> --app-id <APP_ID>`. It deterministically verifies the constraint-following facts a compiler cannot see (exact version pin, no placeholder/fabricated App ID, init in an Application subclass, the verification file guarded by `BuildConfig.DEBUG` so it can't ship to release, `onesignal:managed` markers, the Google Services plugin never applied without a `google-services.json` to process, no deprecated `addOutcome`). If `verdict` is `fail`, repair each error-level check and re-run until it passes; only then declare done. This is the deterministic close of the loop — the agent catches its own slips (e.g. a verification file that names `installIfDebug` but forgets the guard) instead of shipping them. If the script cannot run because Python is missing (Step 0.2), check each item in that list by reading the files you wrote, and say in the summary that the structural self-check did not run.

Before finishing, **scan your own diff for secret-shaped strings** deterministically: `python3 <plugin>/scripts/scan_secrets.py` (scans the working-tree diff; add `--staged` for staged changes, or pass file paths). It flags REST/org keys, `.p8`/service-account contents, and `Authorization: Key` headers while ignoring the public App ID and obvious placeholders, and never prints the matched value — only its file, line, column, and rule. If it exits non-zero, abort and remove the secret — keys live in env vars only (safety contract §"Never"). If the script cannot run because Python is missing (Step 0.2), read `git diff` yourself and look for the same shapes: long key-like strings, `Authorization: Key` headers, `-----BEGIN PRIVATE KEY-----`, and `"private_key"` JSON fields. If you find one, the rule is the same as the scripted path: abort, remove the secret, and do not declare done until the diff is clean.

Expand Down
13 changes: 11 additions & 2 deletions skills/setup/android.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,18 @@

Reference for the `setup` skill. Follow [SKILL.md](SKILL.md) Steps 0–8; this file is the Android install detail. `<plugin>` in the commands below is the directory SKILL.md defines: walk up from this file's directory to the first directory that contains a `scripts/` folder. Mirrors the proven flow in `sdk-ai-prompts/docs/android/integrate.md` with ONE correction from the matrix (below). Do not contradict [../../references/platform-matrix.md](../../references/platform-matrix.md).

## google-services.json is NOT required (matrix correction)
## google-services.json: not required, but recommended

The upstream Android prompt tells you to add `google-services.json` + the Google Services Gradle plugin. **That is a known bug.** OneSignal delivers push via server-side **FCM v1 credentials** (a service-account JSON the human uploads to the OneSignal app, handled by the **credentials** skill). Do NOT add `google-services.json`, the `com.google.gms:google-services` classpath, or `apply plugin: 'com.google.gms.google-services'` — **unless the app itself already uses Firebase client SDKs** (grep for `com.google.firebase` deps / an existing `google-services.json`). If it does, leave the existing Firebase setup alone; still don't add it for OneSignal's sake.
Push delivery does not depend on it. OneSignal sends via server-side **FCM v1 credentials** (a service-account JSON the human uploads to the OneSignal app, handled by the **credentials** skill), so the integration works without `google-services.json`.

It is nonetheless **recommended for new integrations**. On OneSignal Android SDK **5.11.0+**, when the app has its own default `FirebaseApp`, push registers through the customer's Firebase project instead of OneSignal's shared one.

**Agent behavior:**

- Do NOT add or remove `google-services.json`, and do NOT add the `com.google.gms:google-services` classpath or `apply plugin:` line on your own. You cannot produce the file — it comes from the customer's Firebase console — and applying the plugin without the file fails the build with `File google-services.json is missing`.
- If the app already has the file and the plugin, leave both in place.
- If it has neither, continue. Tell the user that adding their own `google-services.json` plus the plugin is recommended, and that they download it from their Firebase console. See [Android Firebase credentials](https://documentation.onesignal.com/docs/en/android-firebase-credentials#add-your-firebase-configuration-file).
- If `firebase_messaging_installation_id_enabled` is `true` in the **merged** manifest — a dependency such as Braze 43.0.0+ can set it through manifest merging — then `google-services.json`, the Gradle plugin, and `firebase-messaging` 25.1.2+ are **required**. Stop and tell the user; without them, push registration fails with subscription status `-32`. See [Firebase Installation ID registration](https://documentation.onesignal.com/docs/en/android-firebase-installation-id).

`POST_NOTIFICATIONS` (Android 13+) is manifest-merged by the SDK — do not add it. Only `INTERNET` needs to be present (it usually already is).

Expand Down
Loading
Loading