@k9o/gate-react-native 1.0.0 → 1.1.1

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,222 @@
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
+ - [Links](#links)
9
+ - [Install](#install)
10
+ - [Quick start](#quick-start)
11
+ - [Refresh and sign out](#refresh-and-sign-out)
12
+ - [Token storage](#token-storage)
13
+ - [Custom claims](#custom-claims)
14
+ - [Handle errors](#handle-errors)
15
+ - [Appendix](#appendix)
16
+
17
+ ## Links
18
+
19
+ - Website: <https://kilimanjaro.dev>
20
+ - Documentation / Developer Hub: <https://docs.kilimanjaro.dev>
21
+ - Platform API: <https://api.k9o.io>
22
+ - Platform Console: <https://console.k9o.io>
23
+ - [Requirements](#requirements)
24
+ - [Client options](#client-options)
25
+ - [Methods](#methods)
26
+ - [Error codes](#error-codes)
27
+ - [Related packages](#related-packages)
28
+
29
+ ## Install
30
+
31
+ ```bash
10
32
  npm install @k9o/gate-react-native react-native-keychain
11
- # or
12
- yarn add @k9o/gate-react-native react-native-keychain
13
33
  ```
14
34
 
15
- ## Quickstart
35
+ Yarn works too: `yarn add @k9o/gate-react-native react-native-keychain`. See [requirements](#requirements) for versions.
36
+
37
+ ## Quick start
38
+
39
+ The examples use a `tasks` app whose users sign in to the `member-portal` project. See [client options](#client-options) for the constructor.
16
40
 
17
41
  ```ts
18
- import { GateClient } from '@k9o/gate-react-native';
42
+ import { GateClient } from "@k9o/gate-react-native";
19
43
 
20
44
  const gate = new GateClient({
21
- orgBaseUrl: 'https://acme.api.k9o.io',
22
- projectSlug: 'my-project',
45
+ orgBaseUrl: "https://acme.api.k9o.io",
46
+ projectSlug: "member-portal",
23
47
  });
48
+ ```
24
49
 
25
- // Sign in
26
- const result = await gate.signIn({ email: 'user@example.com', password: 'password' });
27
- console.log(result.user.id); // eu_...
50
+ Sign in. The refresh token is saved to the keychain for you.
51
+
52
+ ```ts
53
+ const result = await gate.signIn({ email: "user@example.com", password: "correct-horse" });
54
+
55
+ console.log(result.user.email);
28
56
  console.log(result.session.expiresAt);
57
+ ```
58
+
59
+ ```text
60
+ user@example.com
61
+ 2026-01-15T10:30:00.000Z
62
+ ```
63
+
64
+ The result has a `user` and a `session`. See [methods](#methods).
65
+
66
+ ## Refresh and sign out
67
+
68
+ With no arguments, `refreshToken` reads the stored token and saves the new one.
29
69
 
30
- // Refresh (reads Keychain automatically)
70
+ ```ts
31
71
  const refreshed = await gate.refreshToken();
32
72
 
33
- // Sign out (clears Keychain automatically)
73
+ console.log(refreshed.session.expiresAt);
74
+ ```
75
+
76
+ ```text
77
+ 2026-01-15T11:30:00.000Z
78
+ ```
79
+
80
+ `signOut` ends the session on the server and clears the keychain. It does nothing if no token is stored.
81
+
82
+ ```ts
34
83
  await gate.signOut();
35
84
  ```
36
85
 
37
- ## Native Keychain
86
+ ## Token storage
38
87
 
39
- Refresh tokens are persisted via `react-native-keychain` — iOS Keychain / Android Keystore. Inject a custom `TokenStore` for alternative persistence:
88
+ By default the refresh token lives in the iOS Keychain or Android Keystore, through `react-native-keychain`.
89
+
90
+ To store it elsewhere, pass a `TokenStore`. This one keeps it in memory, which suits tests.
40
91
 
41
92
  ```ts
42
- import type { TokenStore } from '@k9o/gate-react-native';
93
+ import { GateClient } from "@k9o/gate-react-native";
94
+ import type { TokenStore } from "@k9o/gate-react-native";
95
+
96
+ class MemoryTokenStore implements TokenStore {
97
+ private token: string | null = null;
98
+
99
+ async save(token: string): Promise<void> {
100
+ this.token = token;
101
+ }
102
+
103
+ async load(): Promise<string | null> {
104
+ return this.token;
105
+ }
106
+
107
+ async delete(): Promise<void> {
108
+ this.token = null;
109
+ }
110
+ }
43
111
 
44
- class MyStore implements TokenStore { ... }
45
112
  const gate = new GateClient({
46
- orgBaseUrl: '...',
47
- projectSlug: '...',
48
- tokenStore: new MyStore(),
113
+ orgBaseUrl: "https://acme.api.k9o.io",
114
+ projectSlug: "member-portal",
115
+ tokenStore: new MemoryTokenStore(),
49
116
  });
50
117
  ```
51
118
 
52
119
  ## Custom claims
53
120
 
121
+ Claims added by extensions appear on the access token under their namespace. Decoding does not verify the signature.
122
+
54
123
  ```ts
55
- import { AccessToken } from '@k9o/gate-react-native';
124
+ import { AccessToken } from "@k9o/gate-react-native";
56
125
 
57
126
  const token = AccessToken.decode(result.session.accessToken);
58
- const role = token?.customClaims['partners']?.['partner_role'];
127
+
128
+ if (token !== null) {
129
+ const claims = token.customClaims;
130
+ const partners = claims["partners"];
131
+
132
+ if (partners !== undefined) {
133
+ const role = partners["partner_role"];
134
+ console.log(role);
135
+ }
136
+ }
59
137
  ```
60
138
 
61
- ## Error handling
139
+ ```text
140
+ extension_org_admin
141
+ ```
142
+
143
+ `token` also has `subject`, `issuer`, `issuedAt`, `expiresAt` and `allClaims`.
144
+
145
+ ## Handle errors
146
+
147
+ Every failure is a `GateError`. Check `code`.
62
148
 
63
149
  ```ts
64
- import { GateError } from '@k9o/gate-react-native';
150
+ import { GateError } from "@k9o/gate-react-native";
65
151
 
66
152
  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
- }
153
+ await gate.signIn({ email: "user@example.com", password: "wrong" });
154
+ } catch (error) {
155
+ if (error instanceof GateError && error.code === "INVALID_CREDENTIALS") {
156
+ console.log(error.message);
75
157
  }
76
158
  }
77
159
  ```
160
+
161
+ ```text
162
+ Invalid credentials.
163
+ ```
164
+
165
+ See [error codes](#error-codes) for all of them.
166
+
167
+ ## Appendix
168
+
169
+ ### Requirements
170
+
171
+ | Requirement | Version |
172
+ |---|---|
173
+ | React | 18 or later |
174
+ | React Native | 0.74 or later |
175
+ | `react-native-keychain` | 8 or later (follow its native setup) |
176
+ | TypeScript (optional) | 5.3 or later |
177
+
178
+ ### Client options
179
+
180
+ `new GateClient({ orgBaseUrl, projectSlug, tokenStore? })`
181
+
182
+ | Option | Required | Meaning |
183
+ |---|---|---|
184
+ | `orgBaseUrl` | Yes | Your organization's API host, such as `https://acme.api.k9o.io` |
185
+ | `projectSlug` | Yes | The project users sign in to |
186
+ | `tokenStore` | No | Where the refresh token is kept; the keychain by default |
187
+
188
+ ### Methods
189
+
190
+ | Method | What it does |
191
+ |---|---|
192
+ | `signIn({ email, password })` | Sign in; resolves to `{ user, session }` |
193
+ | `refreshToken({ refreshToken?, projectHint? })` | Rotate the session; uses the stored token when none is passed |
194
+ | `signOut({ refreshToken? })` | End the session and clear the stored token |
195
+ | `AccessToken.decode(jwt)` | Read an access token's claims; `null` if it is not a JWT |
196
+
197
+ | Result field | Contents |
198
+ |---|---|
199
+ | `user` | `id`, `email`, `status`, `roles`, `groups`, timestamps |
200
+ | `session` | `accessToken`, `refreshToken`, `expiresAt` |
201
+
202
+ ### Error codes
203
+
204
+ | Code | When |
205
+ |---|---|
206
+ | `INVALID_CREDENTIALS` | Wrong email or password |
207
+ | `UNAUTHENTICATED` | The session expired, or no refresh token is stored |
208
+ | `FORBIDDEN` | The session is not allowed to do this |
209
+ | `NETWORK` | The request could not reach the server |
210
+ | `DECODING` | The response was not in the expected shape |
211
+ | `UNKNOWN` | Any other response; `statusCode` and `body` are set |
212
+
213
+ ### Related packages
214
+
215
+ | Package | Purpose |
216
+ |---|---|
217
+ | `@k9o/kibo-react-native` | Client-side encryption with your own KMS |
218
+ | `@k9o/core` | The TypeScript SDK for the rest of the platform |
219
+
220
+ ## License
221
+
222
+ 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.1",
4
4
  "description": "Kilimanjaro Gate IAK SDK for React Native",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -14,7 +14,7 @@
14
14
  "type": "git",
15
15
  "url": "https://github.com/k9o-dev/k9o-gate-react-native.git"
16
16
  },
17
- "homepage": "https://kilimanjaro.dev/sdks/react-native",
17
+ "homepage": "https://kilimanjaro.dev",
18
18
  "keywords": ["kilimanjaro", "gate", "auth", "react-native", "identity"],
19
19
  "license": "Apache-2.0",
20
20
  "publishConfig": {
@@ -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>> {