@layers/amba 1.1.0 → 4.0.2
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.
- package/README.md +1 -1
- package/dist/api-client.d.ts +33 -6
- package/dist/auth.d.ts +8 -0
- package/dist/bundle.d.ts +23 -9
- package/dist/commands/billing.d.ts +34 -0
- package/dist/commands/claim.d.ts +41 -0
- package/dist/commands/init.d.ts +24 -24
- package/dist/commands/projects.d.ts +8 -0
- package/dist/credentials.d.ts +259 -0
- package/dist/index.js +2418 -657
- package/dist/sandbox.d.ts +35 -37
- package/dist/skill-installer.d.ts +95 -0
- package/dist/skills/presets.d.ts +146 -0
- package/dist/skills.d.ts +239 -48
- package/package.json +4 -2
- package/skill-bundle/SKILL.md +324 -0
- package/skill-bundle/references/economy.md +331 -0
- package/skill-bundle/references/engagement.md +400 -0
- package/skill-bundle/references/gamification.md +316 -0
- package/skill-bundle/references/identity.md +395 -0
- package/skill-bundle/references/infrastructure.md +348 -0
- package/skill-bundle/references/social.md +366 -0
|
@@ -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
|