@safercity/sdk 0.1.2 → 0.2.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
@@ -2,6 +2,20 @@
2
2
 
3
3
  Official SaferCity API client for TypeScript/JavaScript.
4
4
 
5
+ ## What's New in v0.2.0
6
+
7
+ - **User-Scoped Client** - The main client is now user-scoped (Stripe publishable key pattern). Admin operations moved to `ServerClient`.
8
+ - **Panic Information** - Full CRUD for user panic profiles and emergency contacts.
9
+ - **Proxy Enhancements** - Added allowlist/blocklist support for proxy endpoints.
10
+ - **Automatic Scoping** - Client now tracks `userId` and automatically scopes requests.
11
+ - **Path Alignment** - All SDK paths now match the latest API schema (singular `/v1/panic`, etc.).
12
+
13
+ ## What's New in v0.1.3
14
+
15
+ - **OAuth endpoint path fix** - Fixed paths (`/oauth/*` → `/v1/oauth/*`)
16
+ - **ServerClient domain helpers** - Added typed domain helpers to `ServerClient`
17
+ - **Security hardening** - Removed `panics.list()` and `subscriptions.stats()` from client-side SDK (available on `ServerClient` only)
18
+
5
19
  ## Installation
6
20
 
7
21
  ```bash
@@ -40,10 +54,12 @@ const client = createSaferCityClient({
40
54
  baseUrl: 'https://api.safercity.com',
41
55
  token: externalAuthToken,
42
56
  tenantId: 'your-tenant-id',
57
+ userId: 'user-123', // optional, for auto-scoping
43
58
  });
44
59
 
45
- // Update token when it changes
60
+ // Update token or user when they change
46
61
  client.setToken(newToken);
62
+ client.setUserId(newUserId);
47
63
  ```
48
64
 
49
65
  ### Cookie Mode
@@ -54,6 +70,7 @@ Browser with `credentials: include`. For first-party web apps using session cook
54
70
  const client = createSaferCityClient({
55
71
  baseUrl: 'https://api.safercity.com',
56
72
  tenantId: 'your-tenant-id',
73
+ userId: 'user-123', // optional
57
74
  });
58
75
  ```
59
76
 
@@ -72,13 +89,12 @@ const client = createSaferCityClient({
72
89
  const { data: health } = await client.health.check();
73
90
  console.log('API Status:', health.status);
74
91
 
75
- // Get user
76
- const { data: user } = await client.users.get('user-123');
92
+ // Get user (auto-resolves userId from client if not passed)
93
+ const { data: user } = await client.users.get();
77
94
  console.log('User:', user);
78
95
 
79
- // Create panic
96
+ // Create panic (userId is optional if set on client)
80
97
  const { data: panic } = await client.panics.create({
81
- userId: 'user-123',
82
98
  latitude: -26.2041,
83
99
  longitude: 28.0473,
84
100
  });
@@ -104,7 +120,26 @@ const client = createServerClient({
104
120
 
105
121
  // All requests are automatically authenticated with OAuth tokens
106
122
  // Tokens are refreshed automatically before expiration
107
- const users = await client.get('/v1/users');
123
+ const { data: users } = await client.users.list(); // Admin only
124
+ const { data: panic } = await client.panics.create({
125
+ userId: 'user-123',
126
+ latitude: -26.2041,
127
+ longitude: 28.0473,
128
+ });
129
+
130
+ // ServerClient has ALL endpoints including admin-only ones:
131
+ // - client.oauth.*
132
+ // - client.tenants.*
133
+ // - client.credentials.*
134
+ // - client.users.list()
135
+ // - client.users.delete()
136
+ // - client.users.updateStatus()
137
+ // - client.panics.list()
138
+ // - client.subscriptions.stats()
139
+ // - client.panicInformation.list()
140
+
141
+ // Low-level requests still work too
142
+ const response = await client.get('/v1/custom-endpoint');
108
143
 
109
144
  // Manual token control
110
145
  const token = await client.getAccessToken();
@@ -112,6 +147,8 @@ await client.refreshToken();
112
147
  client.clearTokens();
113
148
  ```
114
149
 
150
+ > **Note**: The `ServerClient` includes `panics.list()` and `subscriptions.stats()` which are not available on the client-side SDK for security reasons.
151
+
115
152
  ### ServerClientConfig
116
153
 
117
154
  ```typescript
@@ -191,26 +228,15 @@ interface ProxyConfig {
191
228
  pathPrefix?: string; // default: "/api/safercity"
192
229
  forwardHeaders?: string[]; // default: ["content-type", "accept", "x-request-id"]
193
230
  fetch?: typeof fetch;
231
+ allowedEndpoints?: EndpointPattern[]; // Explicit allowlist
232
+ blockedEndpoints?: EndpointPattern[]; // Explicit blocklist
194
233
  }
195
- ```
196
-
197
- ## OAuth Token Flow
198
234
 
199
- ```typescript
200
- const client = createSaferCityClient({
201
- baseUrl: 'https://api.safercity.com',
202
- });
203
-
204
- // Get access token
205
- const { data: tokens } = await client.oauth.token({
206
- grant_type: 'client_credentials',
207
- tenantId: 'your-tenant-id',
208
- });
209
-
210
- // Set token on client
211
- client.setToken(tokens.access_token);
235
+ type EndpointPattern = string | { method?: string; path: string };
212
236
  ```
213
237
 
238
+ Allowlist takes precedence over blocklist. Patterns match by path prefix (case-insensitive).
239
+
214
240
  ## Streaming (SSE)
215
241
 
216
242
  Stream real-time panic updates:
@@ -230,56 +256,51 @@ for await (const event of client.panics.streamUpdates('panic-123')) {
230
256
  - `client.auth.whoami()` - Get current auth context
231
257
  - `client.auth.check()` - Check if authenticated (optional auth)
232
258
 
233
- ### OAuth
234
- - `client.oauth.token(body)` - Get access token
235
- - `client.oauth.refresh(body)` - Refresh token
236
- - `client.oauth.introspect(body)` - Introspect token
237
- - `client.oauth.revoke(body)` - Revoke token
238
-
239
- ### Tenants
240
- - `client.tenants.create(body)` - Create a new tenant
241
- - `client.tenants.list(query?)` - List tenants
242
-
243
- ### Credentials
244
- - `client.credentials.setup(body)` - Exchange setup token for credentials
245
- - `client.credentials.list()` - List credentials
246
- - `client.credentials.revoke(credentialId)` - Revoke credential
247
-
248
- ### Users
259
+ ### Users (User-Scoped)
249
260
  - `client.users.create(body)` - Create user
250
- - `client.users.list(query?)` - List users
251
- - `client.users.get(userId)` - Get user by ID
252
- - `client.users.update(userId, body)` - Update user
253
- - `client.users.updateStatus(userId, body)` - Update user status
254
- - `client.users.delete(userId)` - Delete user
261
+ - `client.users.get(userId?)` - Get user by ID (defaults to client's `userId`)
262
+ - `client.users.update(userId?, body)` - Update user (defaults to client's `userId`)
255
263
 
256
264
  ### Panics
257
265
  - `client.panics.create(body)` - Create panic
258
266
  - `client.panics.get(panicId, query?)` - Get panic
259
- - `client.panics.list(query?)` - List panics
260
267
  - `client.panics.updateLocation(panicId, body)` - Update location
261
268
  - `client.panics.cancel(panicId, body)` - Cancel panic
269
+ - `client.panics.types(userId?)` - Get available panic types for a user
262
270
  - `client.panics.streamUpdates(panicId, options?)` - Stream updates (SSE)
263
271
 
272
+ ### Panic Information
273
+ - `client.panicInformation.create(body)` - Create panic profile
274
+ - `client.panicInformation.get(id)` - Get profile by ID
275
+ - `client.panicInformation.getByUser(userId?)` - Get profile by user ID
276
+ - `client.panicInformation.update(id, body)` - Update profile
277
+ - `client.panicInformation.delete(id)` - Delete profile
278
+ - `client.panicInformation.validateEligibility(userId?)` - Check if user is eligible for panic services
279
+
264
280
  ### Subscriptions
265
281
  - `client.subscriptions.listTypes()` - List subscription types
266
282
  - `client.subscriptions.create(body)` - Create subscription
267
- - `client.subscriptions.list(query?)` - List subscriptions
268
- - `client.subscriptions.stats()` - Get stats
283
+ - `client.subscriptions.list(query?)` - List subscriptions for a user
284
+ - `client.subscriptions.subscribeUser(body)` - Shorthand to subscribe a user
269
285
 
270
286
  ### Notifications
271
287
  - `client.notifications.createSubscriber(body)` - Create subscriber
272
288
  - `client.notifications.trigger(body)` - Trigger notification
273
- - `client.notifications.getPreferences(userId)` - Get user preferences
274
- - `client.notifications.updatePreferences(userId, body)` - Update preferences
289
+ - `client.notifications.bulkTrigger(body)` - Bulk trigger notifications
290
+ - `client.notifications.getPreferences(userId?)` - Get user preferences
291
+ - `client.notifications.updatePreferences(userId?, body)` - Update preferences
275
292
 
276
293
  ### Location Safety
277
- - `client.locationSafety.check(query)` - Check location safety
294
+ - `client.locationSafety.check(body)` - Check location safety (POST)
295
+
296
+ ### Banner
297
+ - `client.banner.get(body)` - Get crime banner data for a location
278
298
 
279
299
  ### Crimes
280
300
  - `client.crimes.list(query?)` - List crimes
281
301
  - `client.crimes.categories()` - Get crime categories
282
302
  - `client.crimes.types()` - Get crime types
303
+ - `client.crimes.categoriesWithTypes()` - Get categories with nested types
283
304
 
284
305
  ## Error Handling
285
306