skikrumb-api 1.3.9 → 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.
- package/.claude/settings.local.json +11 -0
- package/dist/index.d.ts +42 -24
- package/dist/index.js +362 -196
- package/dist/models.d.ts +59 -0
- package/package.json +5 -4
- package/readme.md +1 -1
- package/tsconfig.json +8 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,28 +1,46 @@
|
|
|
1
|
-
import { apiKeys, Device, Gateways, QueryDevice } 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
|
|
4
|
-
supabaseToken
|
|
5
|
-
requestedWith
|
|
6
|
-
url
|
|
26
|
+
apiKey?: string;
|
|
27
|
+
supabaseToken?: string | undefined;
|
|
28
|
+
requestedWith?: string | undefined;
|
|
29
|
+
url?: string | undefined;
|
|
7
30
|
}) => {
|
|
8
|
-
createDevice: (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
updateSubscription: (subscriptionId: string, quantity: number, registrations: Array<string>) => Promise<any>;
|
|
22
|
-
cancelSubscription: (subscriptionId: string) => Promise<any>;
|
|
23
|
-
readApiKeys: () => Promise<apiKeys[]>;
|
|
24
|
-
readDataForDevices: (query: QueryDevice) => Promise<Device[]>;
|
|
25
|
-
readDeviceDailyDistance: (serialNumber: string, query: QueryDevice) => Promise<Device[]>;
|
|
26
|
-
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>;
|
|
27
36
|
readGateways: () => Promise<Gateways>;
|
|
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;
|
|
28
45
|
};
|
|
46
|
+
export type { Device, Gateways, QueryDevice, RateRequest, Rates, apiKeys, ExternalUserRequest, ExternalUserResponse };
|
package/dist/index.js
CHANGED
|
@@ -1,215 +1,381 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
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
|
+
}
|
|
11
211
|
export const skiKrumb = (options = {
|
|
12
212
|
apiKey: '',
|
|
13
|
-
supabaseToken:
|
|
213
|
+
supabaseToken: undefined,
|
|
14
214
|
requestedWith: 'skiKrumb Node & Client API Wrapper',
|
|
15
215
|
url: 'https://api.skikrumb.ca',
|
|
16
216
|
}) => {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
request.headers.set('Content-Type', `application/json`);
|
|
24
|
-
if (options.supabaseToken) {
|
|
25
|
-
request.headers.set('supabase-auth-token', options.supabaseToken);
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
],
|
|
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,
|
|
29
223
|
},
|
|
30
224
|
});
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
.post(`${options.url}/devices`, {
|
|
36
|
-
headers: {
|
|
37
|
-
'content-type': 'application/json',
|
|
38
|
-
},
|
|
39
|
-
json: { deveui: deveui, serial_number: serialNumber },
|
|
40
|
-
})
|
|
225
|
+
// Device API endpoints
|
|
226
|
+
const createDevice = async (device) => {
|
|
227
|
+
const response = await request
|
|
228
|
+
.post('device', { json: device })
|
|
41
229
|
.json();
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
230
|
+
return response;
|
|
231
|
+
};
|
|
232
|
+
const readDevices = async (devices) => {
|
|
233
|
+
try {
|
|
234
|
+
let response;
|
|
235
|
+
if (typeof devices === 'string') {
|
|
236
|
+
response = await request.get(`device/read/${devices}`).json();
|
|
237
|
+
}
|
|
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
|
+
}
|
|
249
|
+
};
|
|
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;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
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
|
+
}
|
|
274
|
+
};
|
|
275
|
+
const readGateways = async () => {
|
|
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
|
+
}
|
|
284
|
+
};
|
|
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
|
+
}
|
|
294
|
+
};
|
|
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
|
+
}
|
|
306
|
+
};
|
|
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
|
+
}
|
|
318
|
+
};
|
|
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,
|
|
65
340
|
},
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
},
|
|
78
|
-
json: payload,
|
|
79
|
-
})
|
|
80
|
-
.json();
|
|
81
|
-
});
|
|
82
|
-
const createPaymentIntent = (form) => __awaiter(void 0, void 0, void 0, function* () {
|
|
83
|
-
if (!form)
|
|
84
|
-
throw new Error('Form values not posted.');
|
|
85
|
-
return api
|
|
86
|
-
.post(`${options.url}/payments/purchase/intent`, {
|
|
87
|
-
body: form,
|
|
88
|
-
})
|
|
89
|
-
.json();
|
|
90
|
-
});
|
|
91
|
-
const getPaymentIntent = (clientSecret) => __awaiter(void 0, void 0, void 0, function* () {
|
|
92
|
-
if (!clientSecret)
|
|
93
|
-
throw new Error('Client secret is required');
|
|
94
|
-
return api.get(`${options.url}/payments/intent/${clientSecret}`).json();
|
|
95
|
-
});
|
|
96
|
-
const createPrePurchaseIntent = (form) => __awaiter(void 0, void 0, void 0, function* () {
|
|
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 = (customerId, email, shipping) => __awaiter(void 0, void 0, void 0, function* () {
|
|
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 = (amount, reference, quantity, address) => __awaiter(void 0, void 0, void 0, function* () {
|
|
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 = (customerId, priceId, cartId, productId, registrations) => __awaiter(void 0, void 0, void 0, function* () {
|
|
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 = (subscriptionId, quantity, registrations) => __awaiter(void 0, void 0, void 0, function* () {
|
|
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();
|
|
155
|
-
});
|
|
156
|
-
const cancelSubscription = (subscriptionId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
157
|
-
if (!subscriptionId) {
|
|
158
|
-
throw new Error('Subscription ID is required');
|
|
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
|
+
};
|
|
159
352
|
}
|
|
160
|
-
|
|
161
|
-
.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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(Object.assign({}, query)),
|
|
170
|
-
})
|
|
171
|
-
.json();
|
|
172
|
-
});
|
|
173
|
-
const readGateways = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
174
|
-
return api.get(`${options.url}/gateways`).json();
|
|
175
|
-
});
|
|
176
|
-
const readDeviceDailyDistance = (serialNumber, query) => __awaiter(void 0, void 0, void 0, function* () {
|
|
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(Object.assign({}, query)),
|
|
182
|
-
})
|
|
183
|
-
.json();
|
|
184
|
-
});
|
|
185
|
-
const readDataForDevices = (query) => __awaiter(void 0, void 0, void 0, function* () {
|
|
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(Object.assign({}, query)),
|
|
191
|
-
})
|
|
192
|
-
.json();
|
|
193
|
-
});
|
|
194
|
-
const readApiKeys = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
195
|
-
return api.get(`${options.url}/keys`).json();
|
|
196
|
-
});
|
|
353
|
+
catch (error) {
|
|
354
|
+
console.error('❌ Token Refresh Error:', error);
|
|
355
|
+
throw error;
|
|
356
|
+
}
|
|
357
|
+
};
|
|
197
358
|
return {
|
|
198
359
|
createDevice,
|
|
199
|
-
|
|
200
|
-
sendMobileLocation,
|
|
201
|
-
createPaymentIntent,
|
|
202
|
-
createPrePurchaseIntent,
|
|
203
|
-
getPaymentIntent,
|
|
204
|
-
updateCustomerAddress,
|
|
205
|
-
getTaxCalculation,
|
|
206
|
-
createSubscription,
|
|
207
|
-
updateSubscription,
|
|
208
|
-
cancelSubscription,
|
|
360
|
+
readDevices,
|
|
209
361
|
readApiKeys,
|
|
210
|
-
readDataForDevices,
|
|
211
362
|
readDeviceDailyDistance,
|
|
212
363
|
readDeviceData,
|
|
213
364
|
readGateways,
|
|
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
|
+
},
|
|
214
380
|
};
|
|
215
381
|
};
|
package/dist/models.d.ts
CHANGED
|
@@ -55,3 +55,62 @@ export interface QueryDevice {
|
|
|
55
55
|
page?: string;
|
|
56
56
|
timeZone?: string;
|
|
57
57
|
}
|
|
58
|
+
export interface Amount {
|
|
59
|
+
value: string;
|
|
60
|
+
currency: string;
|
|
61
|
+
}
|
|
62
|
+
export interface Surcharge {
|
|
63
|
+
type: string;
|
|
64
|
+
amount: Amount;
|
|
65
|
+
}
|
|
66
|
+
export interface Tax {
|
|
67
|
+
type: string;
|
|
68
|
+
amount: Amount;
|
|
69
|
+
}
|
|
70
|
+
export interface ValidUntil {
|
|
71
|
+
year: number;
|
|
72
|
+
month: number;
|
|
73
|
+
day: number;
|
|
74
|
+
}
|
|
75
|
+
export interface Rates {
|
|
76
|
+
service_id: string;
|
|
77
|
+
valid_until: ValidUntil;
|
|
78
|
+
total: Amount;
|
|
79
|
+
base: Amount;
|
|
80
|
+
surcharges: Surcharge[];
|
|
81
|
+
taxes: Tax[];
|
|
82
|
+
transit_time_days: number;
|
|
83
|
+
transit_time_not_available: boolean;
|
|
84
|
+
carrier_name: string;
|
|
85
|
+
service_name: string;
|
|
86
|
+
}
|
|
87
|
+
export interface Address {
|
|
88
|
+
city: string;
|
|
89
|
+
country: string;
|
|
90
|
+
line1: string;
|
|
91
|
+
postal_code: string;
|
|
92
|
+
state: string;
|
|
93
|
+
}
|
|
94
|
+
export interface RateRequest {
|
|
95
|
+
name: string;
|
|
96
|
+
address: Address;
|
|
97
|
+
email_addresses: string[];
|
|
98
|
+
quantity: number;
|
|
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": "
|
|
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,11 +13,12 @@
|
|
|
13
13
|
"author": "",
|
|
14
14
|
"license": "UNLICENSED",
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"
|
|
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
|
-
"ky": "^
|
|
22
|
+
"ky": "^1.7.5"
|
|
22
23
|
}
|
|
23
24
|
}
|
package/readme.md
CHANGED
package/tsconfig.json
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
|
-
"target": "
|
|
3
|
+
"target": "ES2018",
|
|
4
4
|
"module": "ES2020",
|
|
5
5
|
"declaration": true,
|
|
6
6
|
"outDir": "./dist",
|
|
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",
|
|
15
20
|
".idea"
|
|
16
21
|
]
|
|
17
|
-
}
|
|
22
|
+
}
|