@safercity/sdk 0.1.3 → 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,14 @@
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
+
5
13
  ## What's New in v0.1.3
6
14
 
7
15
  - **OAuth endpoint path fix** - Fixed paths (`/oauth/*` → `/v1/oauth/*`)
@@ -46,10 +54,12 @@ const client = createSaferCityClient({
46
54
  baseUrl: 'https://api.safercity.com',
47
55
  token: externalAuthToken,
48
56
  tenantId: 'your-tenant-id',
57
+ userId: 'user-123', // optional, for auto-scoping
49
58
  });
50
59
 
51
- // Update token when it changes
60
+ // Update token or user when they change
52
61
  client.setToken(newToken);
62
+ client.setUserId(newUserId);
53
63
  ```
54
64
 
55
65
  ### Cookie Mode
@@ -60,6 +70,7 @@ Browser with `credentials: include`. For first-party web apps using session cook
60
70
  const client = createSaferCityClient({
61
71
  baseUrl: 'https://api.safercity.com',
62
72
  tenantId: 'your-tenant-id',
73
+ userId: 'user-123', // optional
63
74
  });
64
75
  ```
65
76
 
@@ -78,13 +89,12 @@ const client = createSaferCityClient({
78
89
  const { data: health } = await client.health.check();
79
90
  console.log('API Status:', health.status);
80
91
 
81
- // Get user
82
- 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();
83
94
  console.log('User:', user);
84
95
 
85
- // Create panic
96
+ // Create panic (userId is optional if set on client)
86
97
  const { data: panic } = await client.panics.create({
87
- userId: 'user-123',
88
98
  latitude: -26.2041,
89
99
  longitude: 28.0473,
90
100
  });
@@ -110,13 +120,24 @@ const client = createServerClient({
110
120
 
111
121
  // All requests are automatically authenticated with OAuth tokens
112
122
  // Tokens are refreshed automatically before expiration
113
- const { data: users } = await client.users.list();
123
+ const { data: users } = await client.users.list(); // Admin only
114
124
  const { data: panic } = await client.panics.create({
115
125
  userId: 'user-123',
116
126
  latitude: -26.2041,
117
127
  longitude: 28.0473,
118
128
  });
119
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
+
120
141
  // Low-level requests still work too
121
142
  const response = await client.get('/v1/custom-endpoint');
122
143
 
@@ -207,26 +228,15 @@ interface ProxyConfig {
207
228
  pathPrefix?: string; // default: "/api/safercity"
208
229
  forwardHeaders?: string[]; // default: ["content-type", "accept", "x-request-id"]
209
230
  fetch?: typeof fetch;
231
+ allowedEndpoints?: EndpointPattern[]; // Explicit allowlist
232
+ blockedEndpoints?: EndpointPattern[]; // Explicit blocklist
210
233
  }
211
- ```
212
-
213
- ## OAuth Token Flow
214
-
215
- ```typescript
216
- const client = createSaferCityClient({
217
- baseUrl: 'https://api.safercity.com',
218
- });
219
234
 
220
- // Get access token
221
- const { data: tokens } = await client.oauth.token({
222
- grant_type: 'client_credentials',
223
- tenantId: 'your-tenant-id',
224
- });
225
-
226
- // Set token on client
227
- client.setToken(tokens.access_token);
235
+ type EndpointPattern = string | { method?: string; path: string };
228
236
  ```
229
237
 
238
+ Allowlist takes precedence over blocklist. Patterns match by path prefix (case-insensitive).
239
+
230
240
  ## Streaming (SSE)
231
241
 
232
242
  Stream real-time panic updates:
@@ -246,54 +256,51 @@ for await (const event of client.panics.streamUpdates('panic-123')) {
246
256
  - `client.auth.whoami()` - Get current auth context
247
257
  - `client.auth.check()` - Check if authenticated (optional auth)
248
258
 
249
- ### OAuth
250
- - `client.oauth.token(body)` - Get access token
251
- - `client.oauth.refresh(body)` - Refresh token
252
- - `client.oauth.introspect(body)` - Introspect token
253
- - `client.oauth.revoke(body)` - Revoke token
254
-
255
- ### Tenants
256
- - `client.tenants.create(body)` - Create a new tenant
257
- - `client.tenants.list(query?)` - List tenants
258
-
259
- ### Credentials
260
- - `client.credentials.setup(body)` - Exchange setup token for credentials
261
- - `client.credentials.list()` - List credentials
262
- - `client.credentials.revoke(credentialId)` - Revoke credential
263
-
264
- ### Users
259
+ ### Users (User-Scoped)
265
260
  - `client.users.create(body)` - Create user
266
- - `client.users.list(query?)` - List users
267
- - `client.users.get(userId)` - Get user by ID
268
- - `client.users.update(userId, body)` - Update user
269
- - `client.users.updateStatus(userId, body)` - Update user status
270
- - `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`)
271
263
 
272
264
  ### Panics
273
265
  - `client.panics.create(body)` - Create panic
274
266
  - `client.panics.get(panicId, query?)` - Get panic
275
267
  - `client.panics.updateLocation(panicId, body)` - Update location
276
268
  - `client.panics.cancel(panicId, body)` - Cancel panic
269
+ - `client.panics.types(userId?)` - Get available panic types for a user
277
270
  - `client.panics.streamUpdates(panicId, options?)` - Stream updates (SSE)
278
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
+
279
280
  ### Subscriptions
280
281
  - `client.subscriptions.listTypes()` - List subscription types
281
282
  - `client.subscriptions.create(body)` - Create subscription
282
- - `client.subscriptions.list(query?)` - List subscriptions
283
+ - `client.subscriptions.list(query?)` - List subscriptions for a user
284
+ - `client.subscriptions.subscribeUser(body)` - Shorthand to subscribe a user
283
285
 
284
286
  ### Notifications
285
287
  - `client.notifications.createSubscriber(body)` - Create subscriber
286
288
  - `client.notifications.trigger(body)` - Trigger notification
287
- - `client.notifications.getPreferences(userId)` - Get user preferences
288
- - `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
289
292
 
290
293
  ### Location Safety
291
- - `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
292
298
 
293
299
  ### Crimes
294
300
  - `client.crimes.list(query?)` - List crimes
295
301
  - `client.crimes.categories()` - Get crime categories
296
302
  - `client.crimes.types()` - Get crime types
303
+ - `client.crimes.categoriesWithTypes()` - Get categories with nested types
297
304
 
298
305
  ## Error Handling
299
306