@layers/amba 1.1.0 → 4.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,395 @@
1
+ # Identity
2
+
3
+ End-user authentication for an Amba project: anonymous sessions, email/password, email OTP, SMS OTP, magic links, Sign in with Apple, Sign in with Google, and account linking. All flows return an `AuthResult` containing a `user` + a session token; the SDK persists tokens to the platform's native secure storage and replays them on the next launch. Subsequent SDK calls (collections, push, XP, etc.) are authenticated as the signed-in user automatically.
4
+
5
+ There is no separate "identity provisioning" step — `auth` is the default surface for every project. Your job here is to (a) wire `Amba.configure(...)` plus the right sign-in calls into the user's entry file, and (b) where the user wants social sign-in, set the audience identifiers on the project so the server can verify identity tokens.
6
+
7
+ ## MCP tools
8
+
9
+ | Tool | Purpose | Example args |
10
+ | --- | --- | --- |
11
+ | `amba_developer_me` | Verify the developer PAT and read the developer's profile. Pre-flight check before any provisioning. | `{}` |
12
+ | `amba_projects_get` | Read a project's config (bundle id, OAuth client id, platform). | `{ project_id: "p_…" }` |
13
+ | `amba_projects_update` | Set `bundle_id` (Apple audience) and `google_oauth_client_id` (Google audience). Required before Sign in with Apple / Google works. | `{ project_id, bundle_id: "com.example.fitness", google_oauth_client_id: "1234.apps.googleusercontent.com" }` |
14
+ | `amba_users_list` | Browse end-users (app_users) of the project — useful as a smoke check after the first sign-in. | `{ project_id, limit: 20 }` |
15
+ | `amba_users_get` | Fetch a single app_user by id. | `{ project_id, user_id }` |
16
+ | `amba_users_bulk_update` | Set custom properties on many users at once (e.g. backfilling a `tier` property used for segmentation). | `{ project_id, user_ids: [...], properties: { tier: "trial" } }` |
17
+ | `amba_create_api_key` / `amba_api_keys_create` | Mint additional client/server keys (e.g. a separate `production` key). | `{ project_id, key_type: "client", environment: "production" }` |
18
+ | `amba_delete_api_key` / `amba_api_keys_delete` | Revoke a leaked key. | `{ project_id, api_key_id }` |
19
+ | `amba_assign_role` / `amba_roles_assign` | Grant an RBAC role to an app_user (admin / moderator / etc.). | `{ project_id, user_id, role_id }` |
20
+
21
+ There's no `amba_auth_*` namespace — auth is owned by the SDK on the client side, and there are no provisioning calls for it beyond setting the project's audience identifiers. If the user wants Apple/Google sign-in, the **mandatory** preflight is:
22
+
23
+ ```
24
+ mcp__amba__amba_projects_update({
25
+ project_id: process.env.AMBA_PROJECT_ID,
26
+ bundle_id: "<their iOS bundle id>", // for Apple
27
+ google_oauth_client_id: "<their Google OAuth client id>" // for Google
28
+ })
29
+ ```
30
+
31
+ Without this, the server rejects identity tokens with `AUDIENCE_NOT_CONFIGURED` and the user thinks Amba is broken. If they don't know their bundle id, ask; if they don't have a Google OAuth client yet, tell them to create one at console.cloud.google.com and link it later.
32
+
33
+ ## SDK init per stack
34
+
35
+ ### Expo
36
+
37
+ ```bash
38
+ npx expo install @layers/amba-expo @react-native-async-storage/async-storage
39
+ ```
40
+
41
+ In `app.json` (`expo.plugins` array):
42
+
43
+ ```json
44
+ {
45
+ "expo": {
46
+ "plugins": [
47
+ [
48
+ "@layers/amba-expo",
49
+ {
50
+ "apiKey": "<AMBA_CLIENT_KEY at build time>",
51
+ "scheme": "myapp",
52
+ "googleIosClientId": "<reverse-DNS form of your iOS Google client id>"
53
+ }
54
+ ]
55
+ ]
56
+ }
57
+ }
58
+ ```
59
+
60
+ In `app/_layout.tsx` (or whatever your root layout is):
61
+
62
+ ```tsx
63
+ import { useEffect } from 'react';
64
+ import { Amba } from '@layers/amba-expo';
65
+
66
+ export default function RootLayout() {
67
+ useEffect(() => {
68
+ (async () => {
69
+ await Amba.configure({
70
+ apiKey: process.env.EXPO_PUBLIC_AMBA_CLIENT_KEY!,
71
+ });
72
+ // Anonymous-first. The SDK persists the session; on the next launch
73
+ // this call is a no-op (it returns the stored session).
74
+ await Amba.auth.signInAnonymously();
75
+ })();
76
+ }, []);
77
+ return /* … */ null;
78
+ }
79
+ ```
80
+
81
+ For Sign in with Apple, add `expo-apple-authentication`:
82
+
83
+ ```tsx
84
+ import * as AppleAuthentication from 'expo-apple-authentication';
85
+ import { Amba } from '@layers/amba-expo';
86
+
87
+ async function signInWithApple() {
88
+ const credential = await AppleAuthentication.signInAsync({
89
+ requestedScopes: [
90
+ AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
91
+ AppleAuthentication.AppleAuthenticationScope.EMAIL,
92
+ ],
93
+ });
94
+ if (credential.identityToken) {
95
+ await Amba.auth.signInWithApple(credential.identityToken);
96
+ }
97
+ }
98
+ ```
99
+
100
+ For Sign in with Google, add `expo-auth-session/providers/google`:
101
+
102
+ ```tsx
103
+ import * as Google from 'expo-auth-session/providers/google';
104
+ import { Amba } from '@layers/amba-expo';
105
+
106
+ const [request, response, promptAsync] = Google.useIdTokenAuthRequest({
107
+ clientId: process.env.EXPO_PUBLIC_GOOGLE_CLIENT_ID!,
108
+ });
109
+
110
+ useEffect(() => {
111
+ if (response?.type === 'success' && response.params.id_token) {
112
+ Amba.auth.signInWithGoogle(response.params.id_token);
113
+ }
114
+ }, [response]);
115
+ ```
116
+
117
+ ### React Native (bare)
118
+
119
+ ```bash
120
+ npm install @layers/amba-react-native @react-native-async-storage/async-storage
121
+ ```
122
+
123
+ In `App.tsx` (or `index.js`):
124
+
125
+ ```tsx
126
+ import { useEffect } from 'react';
127
+ import { Amba } from '@layers/amba-react-native';
128
+
129
+ export default function App() {
130
+ useEffect(() => {
131
+ (async () => {
132
+ await Amba.configure({ apiKey: process.env.AMBA_CLIENT_KEY! });
133
+ await Amba.auth.signInAnonymously();
134
+ })();
135
+ }, []);
136
+ return /* … */ null;
137
+ }
138
+ ```
139
+
140
+ For email OTP (works in bare RN without any extra deps):
141
+
142
+ ```tsx
143
+ await Amba.auth.requestEmailOtp(email);
144
+ // user types in the 6-digit code from their inbox
145
+ const result = await Amba.auth.verifyEmailOtp(email, code);
146
+ // result.user is the signed-in app_user
147
+ ```
148
+
149
+ For phone OTP:
150
+
151
+ ```tsx
152
+ // phone must be E.164 (leading "+", 8-15 digits total)
153
+ await Amba.auth.requestSmsOtp('+14155551234');
154
+ await Amba.auth.verifySmsOtp('+14155551234', code);
155
+ ```
156
+
157
+ ### Web (browser / Next.js / Vite / Remix)
158
+
159
+ ```bash
160
+ npm install @layers/amba-web
161
+ # Optional React hooks layer:
162
+ npm install @layers/amba-react
163
+ ```
164
+
165
+ Bare web:
166
+
167
+ ```ts
168
+ import { Amba } from '@layers/amba-web';
169
+
170
+ await Amba.configure({
171
+ apiKey: import.meta.env.VITE_AMBA_CLIENT_KEY,
172
+ });
173
+ await Amba.auth.signInAnonymously();
174
+
175
+ // Magic link
176
+ await Amba.auth.requestMagicLink('user@example.com');
177
+ // (user clicks email → arrives back with ?token=… in the URL)
178
+ const params = new URLSearchParams(window.location.search);
179
+ const token = params.get('token');
180
+ if (token) {
181
+ await Amba.auth.verifyMagicLink(token);
182
+ }
183
+ ```
184
+
185
+ With React hooks (`@layers/amba-react`):
186
+
187
+ ```tsx
188
+ import { Amba } from '@layers/amba-web';
189
+ import { AmbaProvider, useUser } from '@layers/amba-react';
190
+
191
+ await Amba.configure({ apiKey: import.meta.env.VITE_AMBA_CLIENT_KEY });
192
+
193
+ function App() {
194
+ return (
195
+ <AmbaProvider>
196
+ <Inner />
197
+ </AmbaProvider>
198
+ );
199
+ }
200
+
201
+ function Inner() {
202
+ const { user, isAuthenticated, loading } = useUser();
203
+ if (loading) return <Spinner />;
204
+ if (!isAuthenticated) return <SignIn />;
205
+ return <Dashboard user={user!} />;
206
+ }
207
+ ```
208
+
209
+ Next.js — call `Amba.configure(...)` once at the top of `app/layout.tsx` (or `pages/_app.tsx`). Anonymous sign-in should happen on the client; do not call SDK functions in server components or `getServerSideProps`.
210
+
211
+ ### iOS (Swift, SPM)
212
+
213
+ In `Package.swift` (or Xcode → File → Add Package Dependencies):
214
+
215
+ ```swift
216
+ .package(url: "https://github.com/layers/amba-sdk-ios", from: "1.0.0")
217
+ ```
218
+
219
+ In your `App` struct:
220
+
221
+ ```swift
222
+ import SwiftUI
223
+ import Amba
224
+
225
+ @main
226
+ struct MyApp: App {
227
+ init() {
228
+ Task {
229
+ try await Amba.configure(apiKey: ProcessInfo.processInfo.environment["AMBA_CLIENT_KEY"]!)
230
+ try await Amba.auth.signInAnonymously()
231
+ }
232
+ }
233
+ var body: some Scene {
234
+ WindowGroup { ContentView() }
235
+ }
236
+ }
237
+ ```
238
+
239
+ Sign in with Apple — use Apple's `AuthenticationServices` framework:
240
+
241
+ ```swift
242
+ import AuthenticationServices
243
+ import Amba
244
+
245
+ func handleAppleSignIn(authorization: ASAuthorization) async throws {
246
+ guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential,
247
+ let identityTokenData = credential.identityToken,
248
+ let identityToken = String(data: identityTokenData, encoding: .utf8) else { return }
249
+ try await Amba.auth.signInWithApple(identityToken: identityToken)
250
+ }
251
+ ```
252
+
253
+ For Sign in with Google in SwiftUI, use Google's `GoogleSignIn-iOS` SDK, capture the `idToken`, then:
254
+
255
+ ```swift
256
+ try await Amba.auth.signInWithGoogle(idToken: idToken)
257
+ ```
258
+
259
+ > Add the "Sign in with Apple" capability in **Xcode → target → Signing & Capabilities → +Capability**. Without it, the Apple auth call fails before it reaches Amba.
260
+
261
+ ### Android (Kotlin)
262
+
263
+ In `app/build.gradle.kts`:
264
+
265
+ ```kotlin
266
+ dependencies {
267
+ implementation("com.layers.amba:amba-sdk-android:0.1.0")
268
+ }
269
+ ```
270
+
271
+ In your `Application` subclass (create one if missing; register it in `AndroidManifest.xml` `<application android:name=".MyApp">`):
272
+
273
+ ```kotlin
274
+ import android.app.Application
275
+ import com.layers.amba.Amba
276
+ import kotlinx.coroutines.GlobalScope
277
+ import kotlinx.coroutines.launch
278
+
279
+ class MyApp : Application() {
280
+ override fun onCreate() {
281
+ super.onCreate()
282
+ GlobalScope.launch {
283
+ Amba.configure(apiKey = BuildConfig.AMBA_CLIENT_KEY)
284
+ Amba.auth.signInAnonymously()
285
+ }
286
+ }
287
+ }
288
+ ```
289
+
290
+ Wire `AMBA_CLIENT_KEY` via `buildConfigField` in `app/build.gradle.kts`:
291
+
292
+ ```kotlin
293
+ android {
294
+ defaultConfig {
295
+ buildConfigField("String", "AMBA_CLIENT_KEY", "\"${project.findProperty("AMBA_CLIENT_KEY") ?: ""}\"")
296
+ }
297
+ }
298
+ ```
299
+
300
+ Sign in with Google — use Google's `googleid` Credential Manager flow, capture the `idToken`, then:
301
+
302
+ ```kotlin
303
+ Amba.auth.signInWithGoogle(idToken = idToken)
304
+ ```
305
+
306
+ ### Flutter
307
+
308
+ In `pubspec.yaml`:
309
+
310
+ ```yaml
311
+ dependencies:
312
+ amba: ^1.0.0
313
+ ```
314
+
315
+ `flutter pub add amba`.
316
+
317
+ In `lib/main.dart`:
318
+
319
+ ```dart
320
+ import 'package:amba/amba.dart';
321
+ import 'package:flutter/material.dart';
322
+
323
+ Future<void> main() async {
324
+ WidgetsFlutterBinding.ensureInitialized();
325
+ await Amba.configure(apiKey: const String.fromEnvironment('AMBA_CLIENT_KEY'));
326
+ await Amba.auth.signInAnonymously();
327
+ runApp(const MyApp());
328
+ }
329
+ ```
330
+
331
+ Pass the key in: `flutter run --dart-define=AMBA_CLIENT_KEY=$AMBA_CLIENT_KEY`. For Sign in with Apple, add the `sign_in_with_apple` pub package, capture the identity token, then:
332
+
333
+ ```dart
334
+ await Amba.auth.signInWithApple(identityToken: credential.identityToken!);
335
+ ```
336
+
337
+ For Sign in with Google, use `google_sign_in`, capture `googleAuth.idToken`, then:
338
+
339
+ ```dart
340
+ await Amba.auth.signInWithGoogle(idToken: googleAuth.idToken!);
341
+ ```
342
+
343
+ ## Common follow-ups
344
+
345
+ Ask one bundled multi-choice — don't ask one at a time.
346
+
347
+ 1. **Which sign-in methods do you want?** (multi-select)
348
+ - [x] Anonymous (recommended — call at app start, lets users use the app immediately)
349
+ - [ ] Email + password
350
+ - [ ] Email OTP (6-digit code emailed)
351
+ - [ ] Magic link (single click email)
352
+ - [ ] Phone OTP / SMS (E.164, requires SMS provider configured)
353
+ - [ ] Sign in with Apple (iOS / web; required for iOS apps that have any third-party auth per App Store guideline 4.8)
354
+ - [ ] Sign in with Google (Android / iOS / web)
355
+
356
+ 2. **If Apple is selected:** what's your iOS bundle id? (`com.example.fitness`) — required for `bundle_id` on `amba_projects_update`.
357
+
358
+ 3. **If Google is selected:** what's your Google OAuth client id? Format: `123456789-abc.apps.googleusercontent.com`. If they don't have one yet, point them at console.cloud.google.com and proceed without it — they can paste it later with `amba_projects_update`.
359
+
360
+ 4. **If anonymous is selected:** when do you want users to upgrade?
361
+ - On a "Save your progress" prompt (offer Apple/Google linking)
362
+ - Behind a paywall / premium gate
363
+ - Never auto-prompt (user upgrades from settings)
364
+ - Defaults to "never auto-prompt".
365
+
366
+ ## Re-run behavior
367
+
368
+ On a second `/amba` invocation that targets identity:
369
+
370
+ 1. Read `.amba/wired.json` — if `identity` is already listed, skip the SDK install + `Amba.configure(...)` step and only add new sign-in methods.
371
+
372
+ 2. Call `amba_projects_get({ project_id })` to read current `bundle_id` and `google_oauth_client_id`. Compare to what the user gave you:
373
+ - If both already set → no `amba_projects_update` needed.
374
+ - If user is adding a new social provider that needs an audience → call `amba_projects_update` with just the new field. Don't blow away the existing one.
375
+
376
+ 3. For new sign-in methods, append the per-method code block to the existing entry file *without* re-emitting `Amba.configure(...)` (it's already there). Detection: search for `Amba.configure` in the entry file; if present, skip the configure block.
377
+
378
+ 4. Update `.amba/wired.json`:
379
+
380
+ ```json
381
+ {
382
+ "surfaces": {
383
+ "identity": {
384
+ "wired_at": "<original timestamp>",
385
+ "extended_at": "<now>",
386
+ "methods": ["anonymous", "apple", "google", "email_otp"]
387
+ }
388
+ }
389
+ }
390
+ ```
391
+
392
+ 5. If the user asks to "switch from anonymous to email-only" or similar destructive change, **don't auto-do it**. Explain that existing anonymous user data would be unreachable without a link flow, then offer:
393
+ - Add the new method alongside anonymous (recommended)
394
+ - Add a forced upgrade prompt in onboarding
395
+ - Migrate manually via `Amba.auth.linkEmailOtp(email, code)` — keeps existing user data