skikrumb-api 1.4.0 → 2.0.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/.claude/settings.local.json +11 -0
- package/dist/index.d.ts +42 -26
- package/dist/index.js +352 -198
- package/dist/models.d.ts +17 -0
- package/liscense.md +69 -0
- package/package.json +5 -4
- package/tsconfig.json +6 -1
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
|
|
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
|
-
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: (
|
|
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
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
157
|
-
|
|
158
|
-
|
|
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
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
|
|
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
|
-
|
|
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
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
|
|
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
|
|
195
|
-
|
|
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
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
.
|
|
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
|
-
|
|
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/liscense.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Proprietary Use-Only License
|
|
2
|
+
|
|
3
|
+
**Copyright (c) 2025 Skikrumb Trackers Inc.**
|
|
4
|
+
|
|
5
|
+
This license governs your use of **skikrumb-api** (hereafter, "the Software").
|
|
6
|
+
By using, copying, or distributing the Software, you accept and agree to be bound by the terms of this license.
|
|
7
|
+
If you do not agree to these terms, you may not use the Software.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. Definitions
|
|
12
|
+
|
|
13
|
+
- **"The Licensor"** refers to **Skikrumb Trackers Inc.**
|
|
14
|
+
- **"You"** refers to the individual or entity exercising permissions granted by this license.
|
|
15
|
+
- **"The Software"** refers to the source code, object code, documentation, APIs, and any accompanying files provided by the Licensor in the **skikrumb** repository.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 2. Grant of License
|
|
20
|
+
|
|
21
|
+
The Licensor grants you a non-exclusive, royalty-free, non-transferable, revocable license to:
|
|
22
|
+
|
|
23
|
+
- Download, install, and use the Software on any number of devices.
|
|
24
|
+
- Distribute the Software in its original, unmodified form, provided that you include this license in its entirety with all copies of the Software.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 3. Restrictions
|
|
29
|
+
|
|
30
|
+
The rights granted in Section 2 are subject to the following restrictions. You may **NOT**:
|
|
31
|
+
|
|
32
|
+
- Modify, adapt, or translate the Software. Creating derivative works based on the Software is strictly prohibited.
|
|
33
|
+
- Reverse-engineer, decompile, or disassemble the Software, or otherwise attempt to discover the source code of the Software.
|
|
34
|
+
- Sublicense, sell, rent, lease, or lend the Software.
|
|
35
|
+
- Remove or alter any copyright, trademark, patent, or other proprietary notices contained in the Software.
|
|
36
|
+
- Use the Licensor's name, logos, or trademarks (including "Skikrumb") to endorse or promote products derived from the Software without prior written permission.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 4. Intellectual Property
|
|
41
|
+
|
|
42
|
+
All rights, title, and interest in and to the Software, including all copyrights, patents, and trademarks, are and shall remain the exclusive property of the Licensor.
|
|
43
|
+
This license does not grant you any ownership interest in the Software.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 5. Disclaimer of Warranty
|
|
48
|
+
|
|
49
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
50
|
+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
|
|
51
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY,
|
|
52
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 6. Limitation of Liability
|
|
57
|
+
|
|
58
|
+
IN NO EVENT SHALL THE LICENSOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
59
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
60
|
+
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
61
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 7. Termination
|
|
66
|
+
|
|
67
|
+
This license is effective until terminated.
|
|
68
|
+
Your rights under this license will terminate automatically without notice from the Licensor if you fail to comply with any of its terms.
|
|
69
|
+
Upon termination, you must cease all use of the Software and destroy all copies, full or partial, of the Software.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skikrumb-api",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Wrapper for the skiKrumb API",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -11,11 +11,12 @@
|
|
|
11
11
|
},
|
|
12
12
|
"keywords": [],
|
|
13
13
|
"author": "",
|
|
14
|
-
"license": "
|
|
14
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
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
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",
|