Repository navigation
feat: [SDK-5627] teach the setup skill composite login, gated on 5.11.0 - #85
abdulraqeeb33 wants to merge 1 commit into
Conversation
The Android wrapper the skill generates used login(externalId) plus addEmail/addSms, so the generated integration never got the one-request login or any way to set an email/SMS subscription status. Composite login(externalId, profile) only exists on 5.11.0+, and the resolver defaults to --track stable, which is 5.9.2. So this selects the wrapper by the version Step 4 resolved rather than switching the default track, which is a release-policy call. Added a second template for the composite path so the pre-5.11.0 path keeps a verified asset. Verified both directions by compiling the new template in the demo app: clean against published 5.11.0, and failing on 5.9.2 with unresolved OneSignalUserProfile / LoginData / setEmailEnabled, which is what the gate is for. Also corrected a claim I had written into the template and the skill: the wrapper does NOT need kotlinx-coroutines. A suspend fun is backed by kotlin.coroutines.Continuation in kotlin-stdlib. Confirmed by compiling with zero coroutines jars on the classpath. Only the verification helper, which calls launch, needs that dependency. Co-authored-by: Cursor <cursoragent@cursor.com>
3510893 to
ce506f2
Compare
ce506f2 to
3510893
Compare
jfishman1
left a comment
There was a problem hiding this comment.
Approving. The version gate is the right call and I like that the reasoning is in the commit message: composite login doesn’t exist before 5.11.0, stable is still 5.9.2, and rather than quietly flipping the default track to get the newer API you gated on whatever the resolver actually returned and left the track choice to the user. That’s the correct boundary — the alternative would have had the plugin silently moving people onto a non-stable pin.
The verification being real rather than asserted matters too. Compiling both directions — clean on 5.11.0, failing on 5.9.2 with unresolved OneSignalUserProfile/LoginData/setEmailEnabled — is exactly the failure the gate exists to prevent, and you confirmed it actually happens.
Special credit for the coroutines correction. Catching that the wrapper never needed kotlinx-coroutines (a suspend fun is backed by kotlin.coroutines.Continuation in kotlin-stdlib), verifying it by compiling with zero coroutines jars, and still keeping the nuance that the verification helper does need it because it calls launch — that’s a precise fix rather than a blanket reversal, and it removes an unrequested dependency bump the agent would otherwise have made.
The three-way nullability on setEmailEnabled/setSmsEnabled is handled properly, including the warning that guessing true re-subscribes someone who opted out. That’s the whole reason composite login matters for email/SMS and it’d be easy to flatten into a boolean and get wrong.
Two non-blocking notes inline.
Separate heads-up, not about this PR: I have a branch coming that fixes the google-services.json guidance in platform-matrix.md, android.md, the credentials skill, and verify_integration.py. The plugin currently calls our docs’ Firebase guidance “a known bug” — that text came from the July vendor baseline and predates 5.11.0. Your PR doesn’t touch those lines, so there shouldn’t be a conflict, but you’ll see them move.
| val result = OneSignal.login(externalId, profile) | ||
| if (!result.isSuccess) { | ||
| // The device is on the new external ID regardless, so report and move on. | ||
| Log.w(TAG, "login failed: ${result.error?.first?.code} ${result.error?.first?.message}") |
There was a problem hiding this comment.
Nit: this drops everything after the first error. If the backend returns more than one — say a malformed E.164 and a rejected alias label — the generated app logs one and silently discards the rest, which is the worst case for someone debugging their own integration. A joinToString over result.error would cost nothing. Not blocking.
| ## SDK data surface (verified signatures — the codegen targets) | ||
|
|
||
| - `OneSignal.login(externalId[, jwtToken])` / `logout()` — sets/clears external_id. **Call login BEFORE tags/email/sms** or data strands on the anonymous user. | ||
| - `OneSignal.login(externalId, profile[, jwtToken])`: **Android native ≥ 5.11.0 only** (not on iOS or any wrapper SDK yet). Kotlin `suspend fun` returning `OneSignalResult<LoginData>`; from Java pass `Continue.with` (needs API 24+, callback is off the main thread). Sends email, SMS, tags, and aliases inside the login request, so the ordering rule above does not apply to anything in the profile. Build with `OneSignalUserProfile.builder()`: `setEmail`, `setSms` (**E.164 with leading `+`**), `setEmailEnabled`, `setSmsEnabled`, `setTags`, `setAliases`, `build()`. Reserved alias labels (`external_id`, `onesignal_id`) are skipped with a warning, not an error. Prefer this over `login` + `addEmail`/`addSms` whenever the version allows and the contact details are known at sign-in. |
There was a problem hiding this comment.
“Android native ≥ 5.11.0 only (not on iOS or any wrapper SDK yet)” is right today, but it’s a fact with a short shelf life sitting in the file the skill treats as the verified source of truth. Worth a note on where this gets updated when iOS or a wrapper ships it — otherwise the agent will keep telling people it’s Android-only after it isn’t.
One-Line Summary
The setup skill now writes a composite
login(externalId, profile)wrapper when the resolved SDK is 5.11.0 or newer, and keeps the existing wrapper below that.Ticket: SDK-5627
Details
Android 5.11.0 added
login(externalId, profile), which sends email, SMS, tags, and aliases in one Create User upsert and returnsOneSignalResult<LoginData>. The generated wrapper usedlogin(externalId)plusaddEmail/addSms, so an integration the skill produced had no way to set an email or SMS subscription status and discarded the login outcome.android.mdnow picks the wrapper from the version Step 4 resolved, andOneSignalManagerComposite.kt.tmplis the 5.11.0+ asset.api-reference.mdgains the composite signature, thesetEmailEnabled/setSmsEnabledsemantics, and the failure codes.Notes
--track stable, which the feed currently puts at 5.9.2. A wrapper calling composite login against that pin does not compile. Gating leaves the default pin alone; moving the default track is a release-policy decision, not a docs one.Application.kt.tmpl, since the composite wrapper'sinitialize()stays non-suspend.OneSignalUserProfileandsetEmailEnabledare published on the Mobile SDK reference and the Identity Verification page.initWithContextSuspend,getUserSuspend, andgetNotificationsSuspendappear nowhere, so this PR says nothing about them. The skill should not teach an API set a customer cannot look up.kotlinx-coroutineson the compile classpath. It does not: asuspend funis backed bykotlin.coroutines.Continuationin kotlin-stdlib. Left in, it would have had the agent add an unrequested dependency, which safety contract §7 forbids. The template and skill now say so explicitly, because the verification helper genuinely does need that dependency and the distinction is easy to lose.Testing
Compiled in the Android SDK demo app. The version gate is only worth having if it is load-bearing, so it was checked in the failing direction too:
:app:compileGmsDebugKotlin -PSDK_VERSION=5.11.0-PSDK_VERSION=5.9.2OneSignalUserProfile,LoginData,OneSignalResult,setEmailEnabled,setSmsEnabledkotlincagainst the 5.11.0 AARs +android.jarNo eval sessions run. The gate is a decision the skill text makes, and the compile results above are the evidence it is the right one.
Affected areas
skills/setup/SKILL.mdskills/setup/assets/skills/credentials/SKILL.mdskills/verify/SKILL.mdscripts/references/Also
skills/setup/android.md, which the list above does not name.