skikrumb-api 1.4.0 → 2.0.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.
@@ -0,0 +1,11 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Read(//Users/michael/.config/nvim/**)",
5
+ "Read(//Users/michael/.config/**)",
6
+ "Read(//Users/michael/Code/skikrumb-api/**)"
7
+ ],
8
+ "deny": [],
9
+ "ask": []
10
+ }
11
+ }
package/dist/index.d.ts CHANGED
@@ -1,30 +1,46 @@
1
- import { apiKeys, Device, Gateways, QueryDevice, RateRequest, Rates } from './models';
1
+ import { apiKeys, Device, Gateways, QueryDevice, RateRequest, Rates, ExternalUserRequest, ExternalUserResponse } from './models';
2
+ declare class SkiKrumbRealtimeClient {
3
+ private websocket;
4
+ private sessionToken;
5
+ private supabaseToken?;
6
+ private url;
7
+ private userId;
8
+ private listeners;
9
+ private isConnecting;
10
+ private reconnectAttempts;
11
+ private maxReconnectAttempts;
12
+ private reconnectDelay;
13
+ constructor(sessionToken: string, userId: string, url: string, supabaseToken?: string);
14
+ connect(): Promise<void>;
15
+ private connectToWebSocket;
16
+ private scheduleReconnect;
17
+ disconnect(): void;
18
+ ping(): void;
19
+ requestRefresh(): void;
20
+ on(event: string, callback: Function): void;
21
+ off(event: string, callback?: Function): void;
22
+ private emit;
23
+ isConnected(): boolean;
24
+ }
2
25
  export declare const skiKrumb: (options?: {
3
- apiKey: string;
4
- supabaseToken: string;
5
- requestedWith: string | undefined;
6
- url: string | undefined;
26
+ apiKey?: string;
27
+ supabaseToken?: string | undefined;
28
+ requestedWith?: string | undefined;
29
+ url?: string | undefined;
7
30
  }) => {
8
- createDevice: (deveui: string, serialNumber: string) => Promise<any>;
9
- createDeviceDownlink: (userId: string, deveui: string, type?: string) => Promise<any>;
10
- sendMobileLocation: (payload: any) => Promise<any>;
11
- createPaymentIntent: (form: any) => Promise<any>;
12
- createPrePurchaseIntent: (form: any) => Promise<any>;
13
- getPaymentIntent: (clientSecret: string) => Promise<any>;
14
- updateCustomerAddress: (customerId: string, email: string, shipping: object) => Promise<any>;
15
- getTaxCalculation: (amount: number, reference: string, quantity: number, address: {
16
- country: string;
17
- postal_code: string;
18
- state?: string;
19
- }) => Promise<any>;
20
- createSubscription: (customerId: string, priceId: string, cartId: string, productId: string, registrations: Array<string>) => Promise<any>;
21
- updatePaymentIntent: (cartId: string, intentId: string) => Promise<any>;
22
- updateSubscription: (subscriptionId: string, quantity: number, registrations: Array<string>) => Promise<any>;
23
- cancelSubscription: (subscriptionId: string) => Promise<any>;
24
- readApiKeys: () => Promise<apiKeys[]>;
25
- readDataForDevices: (query: QueryDevice) => Promise<Device[]>;
26
- readDeviceDailyDistance: (serialNumber: string, query: QueryDevice) => Promise<Device[]>;
27
- readDeviceData: (serialNumber: string, query: QueryDevice) => Promise<Device>;
31
+ createDevice: (device: Device) => Promise<Device>;
32
+ readDevices: (devices: QueryDevice | string) => Promise<Device[]>;
33
+ readApiKeys: () => Promise<apiKeys>;
34
+ readDeviceDailyDistance: (deviceSerialNumber: string) => Promise<unknown>;
35
+ readDeviceData: (deviceSerialNumber: string, limit?: number) => Promise<unknown>;
28
36
  readGateways: () => Promise<Gateways>;
29
- readShippingRates: (payload: RateRequest) => Promise<Rates[]>;
37
+ readShippingRates: (rateRequest: RateRequest) => Promise<Rates>;
38
+ sendMobileLocation: (payload: {
39
+ deviceId: string;
40
+ locations: any[];
41
+ }) => Promise<unknown>;
42
+ authenticateExternalUser: (userData: ExternalUserRequest) => Promise<ExternalUserResponse>;
43
+ refreshSessionToken: (currentToken: string) => Promise<ExternalUserResponse>;
44
+ createRealtimeClient: (userId: string, sessionToken?: string, supabaseToken?: string) => SkiKrumbRealtimeClient;
30
45
  };
46
+ export type { Device, Gateways, QueryDevice, RateRequest, Rates, apiKeys, ExternalUserRequest, ExternalUserResponse };
package/dist/index.js CHANGED
@@ -1,227 +1,381 @@
1
1
  import ky from 'ky';
2
+ class SkiKrumbRealtimeClient {
3
+ constructor(sessionToken, userId, url, supabaseToken) {
4
+ this.websocket = null;
5
+ this.listeners = {};
6
+ this.isConnecting = false;
7
+ this.reconnectAttempts = 0;
8
+ this.maxReconnectAttempts = 5;
9
+ this.reconnectDelay = 1000;
10
+ this.sessionToken = sessionToken;
11
+ this.supabaseToken = supabaseToken;
12
+ this.userId = userId;
13
+ this.url = url.replace(/\/+$/, ''); // Remove trailing slashes
14
+ }
15
+ async connect() {
16
+ if (this.isConnecting) {
17
+ return Promise.resolve();
18
+ }
19
+ if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
20
+ return Promise.resolve();
21
+ }
22
+ // Additional guard: check if we're already connecting or connected
23
+ if (this.websocket && (this.websocket.readyState === WebSocket.CONNECTING || this.websocket.readyState === WebSocket.OPEN)) {
24
+ return Promise.resolve();
25
+ }
26
+ // Clean up any stale websocket before creating new one
27
+ if (this.websocket && this.websocket.readyState >= WebSocket.CLOSING) {
28
+ this.disconnect();
29
+ }
30
+ this.isConnecting = true;
31
+ try {
32
+ await this.connectToWebSocket();
33
+ }
34
+ catch (error) {
35
+ this.isConnecting = false;
36
+ throw error;
37
+ }
38
+ }
39
+ connectToWebSocket() {
40
+ return new Promise((resolve, reject) => {
41
+ try {
42
+ const baseWsUrl = this.url.replace(/^https/, 'wss').replace(/^http/, 'ws') + '/realtime';
43
+ let wsUrl = `${baseWsUrl}?token=${encodeURIComponent(this.sessionToken)}`;
44
+ if (this.supabaseToken) {
45
+ wsUrl += `&supabaseToken=${encodeURIComponent(this.supabaseToken)}`;
46
+ }
47
+ this.websocket = new WebSocket(wsUrl);
48
+ this.websocket.onopen = () => {
49
+ // Authentication is handled automatically via query parameters
50
+ };
51
+ // Handle all WebSocket messages
52
+ this.websocket.onmessage = (event) => {
53
+ var _a, _b, _c, _d, _e, _f;
54
+ try {
55
+ const message = JSON.parse(event.data);
56
+ if (message.type === 'auth_success') {
57
+ this.isConnecting = false;
58
+ this.reconnectAttempts = 0;
59
+ this.emit('connected', message);
60
+ resolve();
61
+ }
62
+ else if (message.type === 'auth_error' || message.type === 'error') {
63
+ console.error('❌ Authentication/Connection failed:', message.message || message.error);
64
+ (_a = this.websocket) === null || _a === void 0 ? void 0 : _a.close();
65
+ reject(new Error(`Connection failed: ${message.message || message.error}`));
66
+ }
67
+ else if (message.type === 'session_replaced') {
68
+ this.emit('session_replaced', message);
69
+ }
70
+ else if (message.type === 'subscription_updated') {
71
+ this.emit('subscription_updated', message);
72
+ }
73
+ else if (message.type === 'profile_update') {
74
+ this.emit('profile_update', message);
75
+ }
76
+ else if (message.type === 'device_data') {
77
+ // Transform device data to match expected React Native format
78
+ const deviceData = {
79
+ ...message.data,
80
+ device_id: ((_b = message.data.deveui) === null || _b === void 0 ? void 0 : _b.replace(/'/g, '')) || message.data.device_id,
81
+ serial_number: ((_c = message.data.serial_number) === null || _c === void 0 ? void 0 : _c.replace(/'/g, '')) || message.data.serial_number,
82
+ gps_fix: ((_d = message.data.gps_fix) === null || _d === void 0 ? void 0 : _d.replace(/'/g, '')) || message.data.gps_fix,
83
+ gateway_id: ((_e = message.data.gateway_uuid) === null || _e === void 0 ? void 0 : _e.replace(/'/g, '')) || message.data.gateway_id,
84
+ created_at: ((_f = message.data.recorded_at) === null || _f === void 0 ? void 0 : _f.replace(/'/g, '')) || message.data.created_at || message.timestamp,
85
+ timestamp: message.timestamp
86
+ };
87
+ this.emit('device_data', deviceData);
88
+ }
89
+ else if (message.type === 'pong') {
90
+ this.emit('pong', message);
91
+ }
92
+ else {
93
+ // Handle any other message types
94
+ if (message.type) {
95
+ this.emit(message.type, message);
96
+ }
97
+ this.emit('message', message);
98
+ }
99
+ }
100
+ catch (error) {
101
+ console.error('❌ Error parsing realtime message:', error);
102
+ this.emit('error', error);
103
+ }
104
+ };
105
+ this.websocket.onerror = (error) => {
106
+ console.error('❌ Realtime connection error:', error);
107
+ this.isConnecting = false;
108
+ this.emit('error', error);
109
+ reject(error);
110
+ };
111
+ this.websocket.onclose = (event) => {
112
+ this.websocket = null;
113
+ this.isConnecting = false;
114
+ // Don't auto-reconnect if it's a session replacement or normal close
115
+ if (event.code === 4000) {
116
+ this.emit('disconnected', { reason: 'Session replaced' });
117
+ }
118
+ else if (event.code === 1000) {
119
+ this.emit('disconnected', { reason: 'Manual disconnect' });
120
+ }
121
+ else {
122
+ // Only reconnect for unexpected disconnections
123
+ this.emit('disconnected', {
124
+ reason: event.reason || 'Connection closed',
125
+ });
126
+ if (this.reconnectAttempts < this.maxReconnectAttempts) {
127
+ this.scheduleReconnect();
128
+ }
129
+ else {
130
+ this.emit('max_reconnect_attempts_reached');
131
+ }
132
+ }
133
+ };
134
+ }
135
+ catch (error) {
136
+ this.isConnecting = false;
137
+ reject(error);
138
+ }
139
+ });
140
+ }
141
+ scheduleReconnect() {
142
+ this.reconnectAttempts++;
143
+ const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
144
+ setTimeout(() => {
145
+ this.connect().catch((error) => {
146
+ console.error('❌ Reconnect failed:', error);
147
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
148
+ console.error('❌ Max reconnect attempts reached');
149
+ this.emit('max_reconnect_attempts_reached');
150
+ }
151
+ });
152
+ }, delay);
153
+ }
154
+ disconnect() {
155
+ this.isConnecting = false;
156
+ this.reconnectAttempts = 0;
157
+ if (this.websocket) {
158
+ // Remove all listeners to prevent events during cleanup
159
+ this.websocket.onopen = null;
160
+ this.websocket.onmessage = null;
161
+ this.websocket.onerror = null;
162
+ this.websocket.onclose = null;
163
+ // Close the connection if it's still open
164
+ if (this.websocket.readyState === WebSocket.OPEN || this.websocket.readyState === WebSocket.CONNECTING) {
165
+ this.websocket.close(1000, 'Manual disconnect');
166
+ }
167
+ this.websocket = null;
168
+ }
169
+ }
170
+ ping() {
171
+ if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
172
+ this.websocket.send(JSON.stringify({ type: 'ping', timestamp: new Date().toISOString() }));
173
+ }
174
+ }
175
+ requestRefresh() {
176
+ if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
177
+ this.websocket.send(JSON.stringify({ type: 'request_refresh', timestamp: new Date().toISOString() }));
178
+ }
179
+ }
180
+ on(event, callback) {
181
+ if (!this.listeners[event]) {
182
+ this.listeners[event] = [];
183
+ }
184
+ this.listeners[event].push(callback);
185
+ }
186
+ off(event, callback) {
187
+ if (!this.listeners[event])
188
+ return;
189
+ if (callback) {
190
+ this.listeners[event] = this.listeners[event].filter((cb) => cb !== callback);
191
+ }
192
+ else {
193
+ this.listeners[event] = [];
194
+ }
195
+ }
196
+ emit(event, data) {
197
+ const callbacks = this.listeners[event] || [];
198
+ callbacks.forEach((callback) => {
199
+ try {
200
+ callback(data);
201
+ }
202
+ catch (error) {
203
+ console.error(`❌ Error in realtime event listener for '${event}':`, error);
204
+ }
205
+ });
206
+ }
207
+ isConnected() {
208
+ return (this.websocket !== null && this.websocket.readyState === WebSocket.OPEN);
209
+ }
210
+ }
2
211
  export const skiKrumb = (options = {
3
212
  apiKey: '',
4
- supabaseToken: '',
213
+ supabaseToken: undefined,
5
214
  requestedWith: 'skiKrumb Node & Client API Wrapper',
6
215
  url: 'https://api.skikrumb.ca',
7
216
  }) => {
8
- const api = ky.extend({
9
- hooks: {
10
- beforeRequest: [
11
- (request) => {
12
- request.headers.set('X-Requested-With', `${options.requestedWith}`);
13
- request.headers.set('Authorization', `Bearer ${options.apiKey}`);
14
- request.headers.set('Content-Type', `application/json`);
15
- if (options.supabaseToken) {
16
- request.headers.set('supabase-auth-token', options.supabaseToken);
17
- }
18
- },
19
- ],
217
+ const request = ky.create({
218
+ prefixUrl: options.url,
219
+ headers: {
220
+ 'Content-Type': 'application/json',
221
+ Authorization: `Bearer ${options.apiKey}`,
222
+ 'x-client': options.requestedWith,
20
223
  },
21
224
  });
22
- const createDevice = async (deveui, serialNumber) => {
23
- if (!deveui || !serialNumber)
24
- throw new Error('Must pass Lora MAC (deveui) and Serial Number');
25
- return await api
26
- .post(`${options.url}/devices`, {
27
- headers: {
28
- 'content-type': 'application/json',
29
- },
30
- json: { deveui: deveui, serial_number: serialNumber },
31
- })
225
+ // Device API endpoints
226
+ const createDevice = async (device) => {
227
+ const response = await request
228
+ .post('device', { json: device })
32
229
  .json();
230
+ return response;
33
231
  };
34
- const createDeviceDownlink = async (userId, deveui, type = 'beep') => {
35
- if (!userId || !deveui || !type)
36
- throw new Error('Must pass userId, Lora MAC (deveui) and message type');
37
- let message = 'DAgAC04zKE9EPTEwKQ0K';
38
- switch (type) {
39
- case 'help-off':
40
- message = 'DBECAAAAAAAAAAAB';
41
- break;
42
- }
43
- return await api
44
- .post(`${options.url}/devices/downlink`, {
45
- headers: {
46
- 'content-type': 'application/json',
47
- },
48
- json: {
49
- device: deveui,
50
- userId: userId,
51
- data: {
52
- params: {
53
- data: message,
54
- port: 100,
55
- },
56
- },
57
- },
58
- })
59
- .json();
60
- };
61
- const sendMobileLocation = async (payload) => {
62
- if (!payload)
63
- throw new Error('Payload is required');
64
- return await api
65
- .post(`${options.url}/devices/location/mobile`, {
66
- headers: {
67
- 'content-type': 'application/json',
68
- },
69
- json: payload,
70
- })
71
- .json();
72
- };
73
- const createPaymentIntent = async (form) => {
74
- if (!form)
75
- throw new Error('Form values not posted.');
76
- return api
77
- .post(`${options.url}/payments/purchase/intent`, {
78
- body: form,
79
- })
80
- .json();
81
- };
82
- const getPaymentIntent = async (clientSecret) => {
83
- if (!clientSecret)
84
- throw new Error('Client secret is required');
85
- return api.get(`${options.url}/payments/intent/${clientSecret}`).json();
86
- };
87
- const updatePaymentIntent = async (cartId, intentId) => {
88
- if (!cartId || !intentId)
89
- throw new Error('Cart Id and IntentId are required');
90
- return api.patch(`${options.url}/payments/intent/${intentId}`, {
91
- json: {
92
- cartId: cartId,
232
+ const readDevices = async (devices) => {
233
+ try {
234
+ let response;
235
+ if (typeof devices === 'string') {
236
+ response = await request.get(`device/read/${devices}`).json();
93
237
  }
94
- }).json();
95
- };
96
- const createPrePurchaseIntent = async (form) => {
97
- if (!form)
98
- throw new Error('Form values not posted.');
99
- return api
100
- .post(`${options.url}/payments/pre-purchase/intent`, {
101
- body: form,
102
- })
103
- .json();
104
- };
105
- const updateCustomerAddress = async (customerId, email, shipping) => {
106
- if (!customerId || !email || !shipping) {
107
- throw new Error('Customer ID, email, and shipping details are required');
108
- }
109
- return api
110
- .post(`${options.url}/customers/address`, {
111
- json: { customerId, email, shipping },
112
- })
113
- .json();
114
- };
115
- const getTaxCalculation = async (amount, reference, quantity, address) => {
116
- if (!amount || !reference || !quantity || !address) {
117
- throw new Error('Amount, reference, quantity, and address are required');
118
- }
119
- return api
120
- .post(`${options.url}/tax-calculation`, {
121
- json: { amount, reference, quantity, address },
122
- })
123
- .json();
124
- };
125
- const createSubscription = async (customerId, priceId, cartId, productId, registrations) => {
126
- if (!customerId || !priceId || !cartId || !productId || !registrations) {
127
- throw new Error('Customer ID, price Id, cart ID, product ID, and registrations are required');
128
- }
129
- return api
130
- .post(`${options.url}/payments/create-subscription`, {
131
- json: {
132
- customerId,
133
- priceId,
134
- cartId,
135
- productId,
136
- quantity: registrations.length,
137
- registrations,
138
- },
139
- })
140
- .json();
141
- };
142
- const updateSubscription = async (subscriptionId, quantity, registrations) => {
143
- if (!subscriptionId) {
144
- throw new Error('Subscription ID is required');
145
- }
146
- return api
147
- .patch(`${options.url}/payments/update-subscription`, {
148
- json: {
149
- subscriptionId,
150
- quantity,
151
- registrations,
152
- },
153
- })
154
- .json();
238
+ else {
239
+ response = await request
240
+ .post('device/read', { json: devices })
241
+ .json();
242
+ }
243
+ return response;
244
+ }
245
+ catch (error) {
246
+ console.error('❌ Read Devices Error:', error);
247
+ throw error;
248
+ }
155
249
  };
156
- const cancelSubscription = async (subscriptionId) => {
157
- if (!subscriptionId) {
158
- throw new Error('Subscription ID is required');
250
+ const readDeviceData = async (deviceSerialNumber, limit) => {
251
+ try {
252
+ const queryParams = limit ? `?limit=${limit}` : '';
253
+ const response = await request
254
+ .get(`device/data/${deviceSerialNumber}${queryParams}`)
255
+ .json();
256
+ return response;
257
+ }
258
+ catch (error) {
259
+ console.error('❌ Read Device Data Error:', error);
260
+ throw error;
159
261
  }
160
- return api
161
- .delete(`${options.url}/payments/subscription/${subscriptionId}`)
162
- .json();
163
262
  };
164
- const readDeviceData = async (serialNumber, query) => {
165
- if (!serialNumber)
166
- throw new Error('Serial number is required');
167
- return api
168
- .get(`${options.url}/devices/data/${serialNumber}`, {
169
- searchParams: new URLSearchParams({ ...query }),
170
- })
171
- .json();
263
+ const readDeviceDailyDistance = async (deviceSerialNumber) => {
264
+ try {
265
+ const response = await request
266
+ .get(`device/distance/${deviceSerialNumber}`)
267
+ .json();
268
+ return response;
269
+ }
270
+ catch (error) {
271
+ console.error('❌ Read Device Daily Distance Error:', error);
272
+ throw error;
273
+ }
172
274
  };
173
275
  const readGateways = async () => {
174
- return api.get(`${options.url}/gateways`).json();
276
+ try {
277
+ const response = await request.get('gateways').json();
278
+ return response;
279
+ }
280
+ catch (error) {
281
+ console.error('❌ Read Gateways Error:', error);
282
+ throw error;
283
+ }
175
284
  };
176
- const readDeviceDailyDistance = async (serialNumber, query) => {
177
- if (!serialNumber)
178
- throw new Error('Serial number is required');
179
- return api
180
- .get(`${options.url}/devices/${serialNumber}/distance`, {
181
- searchParams: new URLSearchParams({ ...query }),
182
- })
183
- .json();
285
+ const readApiKeys = async () => {
286
+ try {
287
+ const response = await request.get('keys').json();
288
+ return response;
289
+ }
290
+ catch (error) {
291
+ console.error('❌ Read API Keys Error:', error);
292
+ throw error;
293
+ }
184
294
  };
185
- const readDataForDevices = async (query) => {
186
- if (!query.serial_numbers)
187
- throw new Error('Serial number is required');
188
- return api
189
- .get(`${options.url}/devices/data`, {
190
- searchParams: new URLSearchParams({ ...query }),
191
- })
192
- .json();
295
+ const readShippingRates = async (rateRequest) => {
296
+ try {
297
+ const response = await request
298
+ .post('shipping/rates', { json: rateRequest })
299
+ .json();
300
+ return response;
301
+ }
302
+ catch (error) {
303
+ console.error('❌ Read Shipping Rates Error:', error);
304
+ throw error;
305
+ }
193
306
  };
194
- const readApiKeys = async () => {
195
- return api.get(`${options.url}/keys`).json();
307
+ const sendMobileLocation = async (payload) => {
308
+ try {
309
+ const response = await request
310
+ .post('devices/location/mobile', { json: payload })
311
+ .json();
312
+ return response;
313
+ }
314
+ catch (error) {
315
+ console.error('❌ Send Mobile Location Error:', error);
316
+ throw error;
317
+ }
196
318
  };
197
- const readShippingRates = async (payload) => {
198
- return await api
199
- .post(`${options.url}/payments/shipping-rates`, {
200
- headers: {
201
- 'content-type': 'application/json',
202
- },
203
- json: payload,
204
- })
205
- .json();
319
+ const authenticateExternalUser = async (userData) => {
320
+ try {
321
+ const response = await request
322
+ .post('auth/external', { json: userData })
323
+ .json();
324
+ return response;
325
+ }
326
+ catch (error) {
327
+ console.error('❌ External User Authentication Error:', error);
328
+ throw error;
329
+ }
330
+ };
331
+ const refreshSessionToken = async (currentToken) => {
332
+ try {
333
+ // Create temporary request with current token
334
+ const refreshRequest = ky.create({
335
+ prefixUrl: options.url,
336
+ headers: {
337
+ 'Content-Type': 'application/json',
338
+ Authorization: `Bearer ${currentToken}`,
339
+ 'x-client': options.requestedWith,
340
+ },
341
+ });
342
+ const response = await refreshRequest
343
+ .post('auth/external/refresh')
344
+ .json();
345
+ return {
346
+ success: response.success,
347
+ sessionToken: response.sessionToken,
348
+ accessibleSerials: response.accessibleSerials,
349
+ serialsCount: response.serialsCount,
350
+ timestamp: response.timestamp,
351
+ };
352
+ }
353
+ catch (error) {
354
+ console.error('❌ Token Refresh Error:', error);
355
+ throw error;
356
+ }
206
357
  };
207
358
  return {
208
359
  createDevice,
209
- createDeviceDownlink,
210
- sendMobileLocation,
211
- createPaymentIntent,
212
- createPrePurchaseIntent,
213
- getPaymentIntent,
214
- updateCustomerAddress,
215
- getTaxCalculation,
216
- createSubscription,
217
- updatePaymentIntent,
218
- updateSubscription,
219
- cancelSubscription,
360
+ readDevices,
220
361
  readApiKeys,
221
- readDataForDevices,
222
362
  readDeviceDailyDistance,
223
363
  readDeviceData,
224
364
  readGateways,
225
- readShippingRates
365
+ readShippingRates,
366
+ sendMobileLocation,
367
+ authenticateExternalUser,
368
+ refreshSessionToken,
369
+ createRealtimeClient: (userId, sessionToken, supabaseToken) => {
370
+ if (!sessionToken && !options.apiKey) {
371
+ throw new Error('Session token or API key required for realtime client');
372
+ }
373
+ if (!userId) {
374
+ throw new Error('User ID is required for realtime client');
375
+ }
376
+ // Use provided session token or fall back to API key for backward compatibility
377
+ const token = sessionToken || options.apiKey;
378
+ return new SkiKrumbRealtimeClient(token, userId, options.url || 'https://api.skikrumb.ca', supabaseToken);
379
+ },
226
380
  };
227
381
  };
package/dist/models.d.ts CHANGED
@@ -97,3 +97,20 @@ export interface RateRequest {
97
97
  email_addresses: string[];
98
98
  quantity: number;
99
99
  }
100
+ export interface ExternalUserRequest {
101
+ externalUserId: string;
102
+ externalUserEmail?: string;
103
+ nickName?: string;
104
+ providerId?: string;
105
+ providerName?: string;
106
+ }
107
+ export interface ExternalUserResponse {
108
+ success: boolean;
109
+ accountId?: string;
110
+ sessionToken?: string;
111
+ isNewUser?: boolean;
112
+ error?: string;
113
+ accessibleSerials?: string[];
114
+ serialsCount?: number;
115
+ timestamp?: string;
116
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skikrumb-api",
3
- "version": "1.4.0",
3
+ "version": "2.0.0",
4
4
  "description": "Wrapper for the skiKrumb API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -13,9 +13,10 @@
13
13
  "author": "",
14
14
  "license": "UNLICENSED",
15
15
  "devDependencies": {
16
- "typescript": "^5.1.6",
16
+ "@types/node": "^18.19.125",
17
17
  "prettier": "^3.3.3",
18
- "prettier-plugin-organize-attributes": "^1.0.0"
18
+ "prettier-plugin-organize-attributes": "^1.0.0",
19
+ "typescript": "^5.1.6"
19
20
  },
20
21
  "dependencies": {
21
22
  "ky": "^1.7.5"
package/tsconfig.json CHANGED
@@ -7,8 +7,13 @@
7
7
  "strict": true,
8
8
  "esModuleInterop": true,
9
9
  "forceConsistentCasingInFileNames": true,
10
- "moduleResolution":"Node"
10
+ "moduleResolution": "Node",
11
+ "rootDir": "./lib",
12
+ "types": ["node"]
11
13
  },
14
+ "include": [
15
+ "lib/**/*"
16
+ ],
12
17
  "exclude": [
13
18
  "node_modules",
14
19
  "dist",