Engage
The ScaleBun.engage namespace — 16 members.
Engage surface namespace (foundation: transport + native push-token plumbing).
import ScaleBun from '@scalebun/react-native';
ScaleBun.engage.<method>(…);Members#
| Member | Signature |
|---|---|
getIdentifiedUserId | getIdentifiedUserId(): string | undefined |
fetchConfig | fetchConfig(context?: EngageConfigContext): Promise<EngageConfigResponse | null> |
trackCoachmarkEvent | trackCoachmarkEvent(name: string, properties: Record<string, unknown>): void |
clearInAppMessageCache | clearInAppMessageCache(): number |
resetInAppState | resetInAppState(): number |
flushEvents | flushEvents(): void |
debugFetchInAppMessages | debugFetchInAppMessages(context?: EngageConfigContext): Promise<EngageConfigResponse['inAppMessages']> |
submitResponse | submitResponse(input: EngageResponseInput): void |
submitResponseWithAttachments | submitResponseWithAttachments(input: EngageResponseInput, attachments: EngageAttachmentDraft[]): void |
submitResponseConfirmed | submitResponseConfirmed(input: EngageResponseInput, attachments: EngageAttachmentDraft[], opts: { responseId: string; onStage?: (stage: SubmitStage) => void }): Promise<void> |
trackEvent | trackEvent(input: EngageEventInput): void |
trackSurveyImpression | trackSurveyImpression(input: SurveyImpressionInput): void |
emitRatingEvent | emitRatingEvent(input: RatingFunnelEventInput): void |
requestStoreReview | requestStoreReview(): Promise<boolean> |
submitRating | submitRating(input: { rating: number; comment?: string; source?: 'in_app' | 'prompt' | 'manual'; clientRatingId?: string; }): void |
registerForPush | registerForPush(): Promise<void> |
Reference#
getIdentifiedUserId#
ScaleBun.engage.getIdentifiedUserId(): string | undefinedCurrent external identity, forwarded by the provider for segment targeting.
fetchConfig#
ScaleBun.engage.fetchConfig(context?: EngageConfigContext): Promise<EngageConfigResponse | null>Fetch the versioned Engage config (campaigns + in-app messages).
trackCoachmarkEvent#
ScaleBun.engage.trackCoachmarkEvent(name: string, properties: Record<string, unknown>): voidInternal bridge used by the shared renderer to feed standalone tour analytics.
clearInAppMessageCache#
ScaleBun.engage.clearInAppMessageCache(): numberDEBUG/DEV ONLY — clear the persisted in-app/campaign frequency-cap state (every scalebun_engage_throttle:* key: impressions, showOnce/completed, cooldown). Lets a tester see throttled/dismissed messages again WITHOUT uninstalling the app. Does NOT change the device id (identity is preserved). Returns the number of cleared entries. NOT a substitute for the real fix (revision-scoped state + test-preview bypass already make normal Send Test work without this).
resetInAppState#
ScaleBun.engage.resetInAppState(): numberDEBUG/DEV ONLY — alias of {@link clearInAppMessageCache} with the broader name used in docs. Resets all on-device in-app frequency/dismiss/impression state so the next config poll can show messages again. Identity (device id) is untouched.
flushEvents#
ScaleBun.engage.flushEvents(): voidForce an immediate flush of queued in-app events. The provider calls this right after a TEST preview's impression/dismiss ACK so the backend consumes the one-shot preview row promptly — a fast close→kill→reopen can't re-serve a still-delivered test. Best-effort, never throws.
debugFetchInAppMessages#
ScaleBun.engage.debugFetchInAppMessages(context?: EngageConfigContext): Promise<EngageConfigResponse['inAppMessages']>DEBUG/DEV ONLY — force a fresh config fetch (deviceId in context) and return the in-app messages the backend is serving THIS device right now, including any queued test previews (preview:true). Use it to verify Send Test targeting/ delivery without rendering. Logs a one-line summary for the dev console.
submitResponse#
ScaleBun.engage.submitResponse(input: EngageResponseInput): voidUpload a survey/NPS/feedback response (idempotent, rides the outbox).
submitResponseWithAttachments#
ScaleBun.engage.submitResponseWithAttachments(input: EngageResponseInput, attachments: EngageAttachmentDraft[]): void[P3-e] Submit a response carrying native attachments (screenshot/voice). Presigns + PUTs bytes direct to storage, then enqueues the response with key-only metadata linked by responseId. Falls back to a plain response (no media) when storage is disabled. Best-effort, never throws.
submitResponseConfirmed#
ScaleBun.engage.submitResponseConfirmed(input: EngageResponseInput, attachments: EngageAttachmentDraft[], opts: { responseId: string; onStage?: (stage: SubmitStage) => void }): Promise<void>[Slice D5] CONFIRMED response submit for the in-prompt submitting→success→error UI. UNLIKE submitResponse*, this AWAITS real server persistence (presign+PUT any audio/screenshot bytes, then a direct response POST) and REJECTS on failure so the caller can show a retryable error. Idempotent: pass the SAME responseId on retry — both response and attachments de-dupe server-side. Used by EVERY question type.
trackEvent#
ScaleBun.engage.trackEvent(input: EngageEventInput): voidUpload an in-app impression/click/dismiss event (idempotent, rides the outbox).
trackSurveyImpression#
ScaleBun.engage.trackSurveyImpression(input: SurveyImpressionInput): voidUpload an idempotent survey shown/started funnel beacon.
emitRatingEvent#
ScaleBun.engage.emitRatingEvent(input: RatingFunnelEventInput): voidEmit a rating-funnel tracking event (ratings-rebuild CONTRACT §1.2). These are dimensioned FACTS feeding the Overview funnel — NOT campaign responses (D1) — so they ride their own /ingestion/rating-events lane. Idempotent (client eventId, D8). Best-effort, never throws.
requestStoreReview#
ScaleBun.engage.requestStoreReview(): Promise<boolean>Trigger the native in-app review sheet (Play In-App Review on Android, SKStoreReviewController on iOS) — the genuinely-native leg of the rating funnel (D5). Resolves true ONLY when the OS flow ran/resolved after a positive route, so the caller emits store_redirect_clicked solely on a true result. Fail-soft: resolves false when native review is unavailable.
submitRating#
ScaleBun.engage.submitRating(input: { rating: number; comment?: string; source?: 'in_app' | 'prompt' | 'manual'; clientRatingId?: string; }): voidSubmit a 1–5 app-store rating via the EXISTING app-rating lane (D1 keeps ratings a distinct funnel — NOT submitResponse). Delegates to the facade's submitRating, threading the renderer's clientRatingId for idempotency (D8). Used by the RATING_PROMPT render branch.
registerForPush#
ScaleBun.engage.registerForPush(): Promise<void>Acquire the native push token (FCM/APNs) and register it for delivery. Subscribes to native token-refresh (rotation) so the backend always holds the live token. Fail-soft: no-op when native push is unavailable.
Notes#
engage is the largest namespace here and the one whose scope is easiest to overestimate. It covers
in-app messaging, survey and rating responses, the store-review prompt, and push registration.
Ordering#
init(), then identify(), then anything targeted
getIdentifiedUserId()returningundefinedis the check for whether targeting can work at all. An unidentified user cannot be reached by an audience built from people.fetchConfig() before you expect a message
Nothing is presented from configuration you have not fetched. It takes an optional context, which is what lets targeting depend on where the user currently is.
Submit a response once, and prefer the confirmed variant when it matters
submitResponseis fire-and-forget.submitResponseConfirmedawaits and reports stage progress, which is what you want when the user is watching a spinner or when attachments are involved.flushEvents() before a boundary you may not survive
Sign-out, or the end of a flow. Otherwise let it batch.
Members worth specific care#
requestStoreReview()returns a boolean, and on iOS that boolean does not mean the user rated — or even that the prompt appeared. The OS decides, silently rate-limits, and reports nothing back. See Ratings for what is and is not measurable.submitRatingtakes an optionalclientRatingId, which exists for idempotency. Generate one per rating and reuse it on retry, or a resubmitted rating counts twice.clearInAppMessageCache()andresetInAppState()return the number of entries cleared. They are development and support tools — calling them in a normal app flow re-presents messages the user has already dismissed.registerForPush()overlaps withpushand withenablePush()on the facade. Pick one path.submitResponseWithAttachmentsuploads user-provided files. Whatever the user attaches leaves the device — worth stating in your own UI.
Limits and edge cases#
These calls do not throw and are silent in release builds, so a failed submission looks exactly like a successful one.
Suppression is enforced server-side. A user on a suppression list is not messaged regardless of what you fetch or submit.
Cached messages are per device. Clearing on one device does not clear another.