@k9o/gate-react-native 1.0.0 → 1.1.0

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 CHANGED
@@ -1,77 +1,216 @@
1
1
  # @k9o/gate-react-native
2
2
 
3
- The official React Native SDK for the [Kilimanjaro](https://kilimanjaro.dev) Gate Identity-Aware Kernel.
4
-
5
- **Version**: 0.3.0 | **Platform**: RN 0.74+ (New Architecture) | **TypeScript**: 5.3+
6
-
7
- ## Installation
8
-
9
- ```sh
3
+ Kilimanjaro Gate for React Native. Sign end users in with email and password and refresh their session.
4
+ The refresh token stays in the device keychain.
5
+
6
+ ## Contents
7
+
8
+ - [Install](#install)
9
+ - [Quick start](#quick-start)
10
+ - [Refresh and sign out](#refresh-and-sign-out)
11
+ - [Token storage](#token-storage)
12
+ - [Custom claims](#custom-claims)
13
+ - [Handle errors](#handle-errors)
14
+ - [Appendix](#appendix)
15
+ - [Requirements](#requirements)
16
+ - [Client options](#client-options)
17
+ - [Methods](#methods)
18
+ - [Error codes](#error-codes)
19
+ - [Related packages](#related-packages)
20
+
21
+ ## Install
22
+
23
+ ```bash
10
24
  npm install @k9o/gate-react-native react-native-keychain
11
- # or
12
- yarn add @k9o/gate-react-native react-native-keychain
13
25
  ```
14
26
 
15
- ## Quickstart
27
+ Yarn works too: `yarn add @k9o/gate-react-native react-native-keychain`. See [requirements](#requirements) for versions.
28
+
29
+ ## Quick start
30
+
31
+ The examples use a `tasks` app whose users sign in to the `member-portal` project. See [client options](#client-options) for the constructor.
16
32
 
17
33
  ```ts
18
- import { GateClient } from '@k9o/gate-react-native';
34
+ import { GateClient } from "@k9o/gate-react-native";
19
35
 
20
36
  const gate = new GateClient({
21
- orgBaseUrl: 'https://acme.api.k9o.io',
22
- projectSlug: 'my-project',
37
+ orgBaseUrl: "https://acme.api.k9o.io",
38
+ projectSlug: "member-portal",
23
39
  });
40
+ ```
24
41
 
25
- // Sign in
26
- const result = await gate.signIn({ email: 'user@example.com', password: 'password' });
27
- console.log(result.user.id); // eu_...
42
+ Sign in. The refresh token is saved to the keychain for you.
43
+
44
+ ```ts
45
+ const result = await gate.signIn({ email: "user@example.com", password: "correct-horse" });
46
+
47
+ console.log(result.user.email);
28
48
  console.log(result.session.expiresAt);
49
+ ```
50
+
51
+ ```text
52
+ user@example.com
53
+ 2026-01-15T10:30:00.000Z
54
+ ```
55
+
56
+ The result has a `user` and a `session`. See [methods](#methods).
57
+
58
+ ## Refresh and sign out
59
+
60
+ With no arguments, `refreshToken` reads the stored token and saves the new one.
29
61
 
30
- // Refresh (reads Keychain automatically)
62
+ ```ts
31
63
  const refreshed = await gate.refreshToken();
32
64
 
33
- // Sign out (clears Keychain automatically)
65
+ console.log(refreshed.session.expiresAt);
66
+ ```
67
+
68
+ ```text
69
+ 2026-01-15T11:30:00.000Z
70
+ ```
71
+
72
+ `signOut` ends the session on the server and clears the keychain. It does nothing if no token is stored.
73
+
74
+ ```ts
34
75
  await gate.signOut();
35
76
  ```
36
77
 
37
- ## Native Keychain
78
+ ## Token storage
79
+
80
+ By default the refresh token lives in the iOS Keychain or Android Keystore, through `react-native-keychain`.
38
81
 
39
- Refresh tokens are persisted via `react-native-keychain` — iOS Keychain / Android Keystore. Inject a custom `TokenStore` for alternative persistence:
82
+ To store it elsewhere, pass a `TokenStore`. This one keeps it in memory, which suits tests.
40
83
 
41
84
  ```ts
42
- import type { TokenStore } from '@k9o/gate-react-native';
85
+ import { GateClient } from "@k9o/gate-react-native";
86
+ import type { TokenStore } from "@k9o/gate-react-native";
87
+
88
+ class MemoryTokenStore implements TokenStore {
89
+ private token: string | null = null;
90
+
91
+ async save(token: string): Promise<void> {
92
+ this.token = token;
93
+ }
94
+
95
+ async load(): Promise<string | null> {
96
+ return this.token;
97
+ }
98
+
99
+ async delete(): Promise<void> {
100
+ this.token = null;
101
+ }
102
+ }
43
103
 
44
- class MyStore implements TokenStore { ... }
45
104
  const gate = new GateClient({
46
- orgBaseUrl: '...',
47
- projectSlug: '...',
48
- tokenStore: new MyStore(),
105
+ orgBaseUrl: "https://acme.api.k9o.io",
106
+ projectSlug: "member-portal",
107
+ tokenStore: new MemoryTokenStore(),
49
108
  });
50
109
  ```
51
110
 
52
111
  ## Custom claims
53
112
 
113
+ Claims added by extensions appear on the access token under their namespace. Decoding does not verify the signature.
114
+
54
115
  ```ts
55
- import { AccessToken } from '@k9o/gate-react-native';
116
+ import { AccessToken } from "@k9o/gate-react-native";
56
117
 
57
118
  const token = AccessToken.decode(result.session.accessToken);
58
- const role = token?.customClaims['partners']?.['partner_role'];
119
+
120
+ if (token !== null) {
121
+ const claims = token.customClaims;
122
+ const partners = claims["partners"];
123
+
124
+ if (partners !== undefined) {
125
+ const role = partners["partner_role"];
126
+ console.log(role);
127
+ }
128
+ }
129
+ ```
130
+
131
+ ```text
132
+ extension_org_admin
59
133
  ```
60
134
 
61
- ## Error handling
135
+ `token` also has `subject`, `issuer`, `issuedAt`, `expiresAt` and `allClaims`.
136
+
137
+ ## Handle errors
138
+
139
+ Every failure is a `GateError`. Check `code`.
62
140
 
63
141
  ```ts
64
- import { GateError } from '@k9o/gate-react-native';
142
+ import { GateError } from "@k9o/gate-react-native";
65
143
 
66
144
  try {
67
- await gate.signIn({ email, password });
68
- } catch (e) {
69
- if (e instanceof GateError) {
70
- switch (e.code) {
71
- case 'INVALID_CREDENTIALS': break;
72
- case 'NETWORK': break;
73
- case 'UNKNOWN': console.log(e.statusCode, e.body); break;
74
- }
145
+ await gate.signIn({ email: "user@example.com", password: "wrong" });
146
+ } catch (error) {
147
+ if (error instanceof GateError && error.code === "INVALID_CREDENTIALS") {
148
+ console.log(error.message);
75
149
  }
76
150
  }
77
151
  ```
152
+
153
+ ```text
154
+ Invalid credentials.
155
+ ```
156
+
157
+ See [error codes](#error-codes) for all of them.
158
+
159
+ ## Appendix
160
+
161
+ ### Requirements
162
+
163
+ | Requirement | Version |
164
+ |---|---|
165
+ | React | 18 or later |
166
+ | React Native | 0.74 or later |
167
+ | `react-native-keychain` | 8 or later (follow its native setup) |
168
+ | TypeScript (optional) | 5.3 or later |
169
+
170
+ ### Client options
171
+
172
+ `new GateClient({ orgBaseUrl, projectSlug, tokenStore? })`
173
+
174
+ | Option | Required | Meaning |
175
+ |---|---|---|
176
+ | `orgBaseUrl` | Yes | Your organization's API host, such as `https://acme.api.k9o.io` |
177
+ | `projectSlug` | Yes | The project users sign in to |
178
+ | `tokenStore` | No | Where the refresh token is kept; the keychain by default |
179
+
180
+ ### Methods
181
+
182
+ | Method | What it does |
183
+ |---|---|
184
+ | `signIn({ email, password })` | Sign in; resolves to `{ user, session }` |
185
+ | `refreshToken({ refreshToken?, projectHint? })` | Rotate the session; uses the stored token when none is passed |
186
+ | `signOut({ refreshToken? })` | End the session and clear the stored token |
187
+ | `AccessToken.decode(jwt)` | Read an access token's claims; `null` if it is not a JWT |
188
+
189
+ | Result field | Contents |
190
+ |---|---|
191
+ | `user` | `id`, `email`, `status`, `roles`, `groups`, timestamps |
192
+ | `session` | `accessToken`, `refreshToken`, `expiresAt` |
193
+
194
+ ### Error codes
195
+
196
+ | Code | When |
197
+ |---|---|
198
+ | `INVALID_CREDENTIALS` | Wrong email or password |
199
+ | `UNAUTHENTICATED` | The session expired, or no refresh token is stored |
200
+ | `FORBIDDEN` | The session is not allowed to do this |
201
+ | `NETWORK` | The request could not reach the server |
202
+ | `DECODING` | The response was not in the expected shape |
203
+ | `UNKNOWN` | Any other response; `statusCode` and `body` are set |
204
+
205
+ ### Related packages
206
+
207
+ | Package | Purpose |
208
+ |---|---|
209
+ | `@k9o/kibo-react-native` | Client-side encryption with your own KMS |
210
+ | `@k9o/core` | The TypeScript SDK for the rest of the platform |
211
+
212
+ Documentation: <https://kilimanjaro.dev>
213
+
214
+ ## License
215
+
216
+ Apache-2.0 © Formelo Inc. See `LICENSE` and `NOTICE`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k9o/gate-react-native",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Kilimanjaro Gate IAK SDK for React Native",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -3,7 +3,7 @@ import { parseEnvelope } from "./Models";
3
3
 
4
4
  /**
5
5
  * Internal fetch-based transport layer for @k9o/gate-react-native.
6
- * Constructs URLs using the v2.5.68 `/@<projectSlug>/gate/<suffix>` grammar.
6
+ * Constructs URLs using the `/@<projectSlug>/gate/<suffix>` grammar.
7
7
  */
8
8
  export class GateAuthClient {
9
9
  private readonly baseUrl: string;
package/src/GateClient.ts CHANGED
@@ -63,7 +63,7 @@ export class GateClient {
63
63
  * If `refreshToken` is omitted, loads the token from the store.
64
64
  * Updates the store with the new refresh token on success.
65
65
  *
66
- * `projectHint` is optional per ch03 §3.9; omit for most configurations.
66
+ * `projectHint` is optional; omit for most configurations.
67
67
  *
68
68
  * Mirrors: `POST /@<projectSlug>/gate/refresh`
69
69
  */
package/src/GateError.ts CHANGED
@@ -67,9 +67,9 @@ export class GateError extends Error {
67
67
 
68
68
  // MARK: - Wire error parsing
69
69
 
70
- // The gateway emits a KError at the ROOT of the error body per ch01 §10.4:
70
+ // The gateway emits a KError at the ROOT of the error body:
71
71
  // `{ code, message, context?, recommendation? }` — NOT wrapped in `{ error: … }`.
72
- // Codes are platform-namespaced (`K9O_*` per the ch11 error namespace), so the
72
+ // Codes are platform-namespaced (`K9O_*`), so the
73
73
  // prefix is stripped before matching the SDK's typed enum.
74
74
  interface WireKError {
75
75
  code?: string;
package/src/Models.ts CHANGED
@@ -29,7 +29,7 @@ export interface AuthResult {
29
29
  session: Session;
30
30
  }
31
31
 
32
- /** Standard ch01 §1 response envelope. */
32
+ /** Standard response envelope. */
33
33
  interface Envelope<T> {
34
34
  data: T;
35
35
  meta?: Record<string, unknown>;
@@ -54,7 +54,7 @@ export function parseEnvelope<T>(body: string): T {
54
54
  /**
55
55
  * Lightweight access-token decoder. Decodes the JWT payload without
56
56
  * signature verification. Exposes `ext.<namespace>.<name>` custom-claim
57
- * flatten from v2.5.65.
57
+ * flatten from.
58
58
  */
59
59
  export class AccessToken {
60
60
  readonly subject: string | undefined;
@@ -72,7 +72,7 @@ export class AccessToken {
72
72
  }
73
73
 
74
74
  /**
75
- * Custom claims namespaced under `ext.<namespace>` per v2.5.65.
75
+ * Custom claims namespaced under `ext.<namespace>` per.
76
76
  * `customClaims['partners']?.['partner_role']` → "extension_org_admin"
77
77
  */
78
78
  get customClaims(): Record<string, Record<string, unknown>> {