cf-service-sdk 0.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.
@@ -0,0 +1,25 @@
1
+ import { ApolloClient, NormalizedCacheObject } from '@apollo/client';
2
+ export interface CloudForgeClientOptions {
3
+ baseUrl: string;
4
+ token?: string;
5
+ onUnauthorized?: () => void;
6
+ onError?: (error: Error) => void;
7
+ onValidationError?: (validationErrors: ValidationError[]) => void;
8
+ refreshToken?: () => Promise<string | null>;
9
+ }
10
+ export interface ValidationError {
11
+ field: string;
12
+ message: string;
13
+ }
14
+ export declare class CloudForgeClient {
15
+ private apolloClient;
16
+ private options;
17
+ private isRefreshing;
18
+ constructor(options: CloudForgeClientOptions);
19
+ private createApolloClient;
20
+ setToken(token: string): void;
21
+ getClient(): ApolloClient<NormalizedCacheObject>;
22
+ getBaseUrl(): string;
23
+ refreshToken(): Promise<boolean>;
24
+ logout(): void;
25
+ }
package/dist/client.js ADDED
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CloudForgeClient = void 0;
4
+ const client_1 = require("@apollo/client");
5
+ const error_1 = require("@apollo/client/link/error");
6
+ const context_1 = require("@apollo/client/link/context");
7
+ const defaultOptions = {
8
+ baseUrl: 'http://localhost:8000/api/graph/',
9
+ };
10
+ // Detect if we're in a React Native environment
11
+ const isReactNative = () => {
12
+ return typeof global.HermesInternal !== 'undefined' ||
13
+ typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
14
+ };
15
+ class CloudForgeClient {
16
+ constructor(options) {
17
+ this.isRefreshing = false;
18
+ this.options = { ...defaultOptions, ...options };
19
+ this.apolloClient = this.createApolloClient();
20
+ }
21
+ createApolloClient() {
22
+ // Mobile device workaround: replace localhost with the device's network IP
23
+ // This is needed because localhost on a mobile device refers to the device itself
24
+ let apiUrl = this.options.baseUrl;
25
+ // Note: We previously modified the URL here, but now we expect the URL to be
26
+ // properly configured by the application that uses this SDK, especially for React Native
27
+ // applications where localhost means different things on different platforms.
28
+ // HTTP link
29
+ const httpLink = (0, client_1.createHttpLink)({
30
+ uri: apiUrl,
31
+ });
32
+ // Auth link for adding the token to headers
33
+ const authLink = (0, context_1.setContext)((_, { headers }) => {
34
+ const token = this.options.token;
35
+ return {
36
+ headers: {
37
+ ...headers,
38
+ authorization: token ? `JWT ${token}` : '',
39
+ },
40
+ };
41
+ });
42
+ // Error handling link
43
+ const errorLink = (0, error_1.onError)(({ graphQLErrors, networkError, operation, forward }) => {
44
+ var _a, _b, _c, _d;
45
+ console.log('==== ERROR LINK ====');
46
+ console.log('==== GRAPHQL ERRORS ====');
47
+ console.log(graphQLErrors);
48
+ console.log('==== NETWORK ERROR ====');
49
+ console.log(networkError);
50
+ console.log('==== OPERATION ====');
51
+ console.log(operation);
52
+ if (graphQLErrors) {
53
+ const validationErrors = [];
54
+ graphQLErrors.forEach(({ message, locations, path }) => {
55
+ var _a, _b;
56
+ console.error(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`);
57
+ // Check for unauthorized errors
58
+ if (message.includes('Authentication') ||
59
+ message.includes('Error decoding signature') ||
60
+ message.includes('JWT') ||
61
+ message.toLowerCase().includes('token') ||
62
+ message.includes('Signature has expired')) {
63
+ console.log('==== JWT ERROR DETECTED ====', message);
64
+ // Try to refresh the token if a refresh function is provided
65
+ if (this.options.refreshToken && !this.isRefreshing) {
66
+ this.isRefreshing = true;
67
+ // Attempt to refresh the token
68
+ this.options.refreshToken()
69
+ .then(newToken => {
70
+ var _a, _b;
71
+ if (newToken) {
72
+ console.log('==== TOKEN REFRESHED ====');
73
+ // Set the new token
74
+ this.setToken(newToken);
75
+ // Retry the failed request
76
+ const oldHeaders = operation.getContext().headers;
77
+ operation.setContext({
78
+ headers: {
79
+ ...oldHeaders,
80
+ authorization: `JWT ${newToken}`,
81
+ },
82
+ });
83
+ // Retry the operation
84
+ return forward(operation);
85
+ }
86
+ else {
87
+ console.log('==== TOKEN REFRESH FAILED ====');
88
+ // If refresh token failed, call onUnauthorized
89
+ (_b = (_a = this.options).onUnauthorized) === null || _b === void 0 ? void 0 : _b.call(_a);
90
+ }
91
+ })
92
+ .catch(error => {
93
+ var _a, _b;
94
+ console.error('Token refresh failed:', error);
95
+ (_b = (_a = this.options).onUnauthorized) === null || _b === void 0 ? void 0 : _b.call(_a);
96
+ })
97
+ .finally(() => {
98
+ this.isRefreshing = false;
99
+ });
100
+ }
101
+ else {
102
+ // If no refresh function is provided, just call onUnauthorized
103
+ (_b = (_a = this.options).onUnauthorized) === null || _b === void 0 ? void 0 : _b.call(_a);
104
+ }
105
+ }
106
+ // Check for validation errors
107
+ if (message.includes('got invalid value') || message.includes('Field') && message.includes('was not provided')) {
108
+ // Extract field name from validation error
109
+ const fieldMatch = message.match(/Field '([^']+)'/);
110
+ if (fieldMatch && fieldMatch[1]) {
111
+ validationErrors.push({
112
+ field: fieldMatch[1],
113
+ message: message.trim()
114
+ });
115
+ }
116
+ }
117
+ });
118
+ // If we found validation errors, call the validation error handler
119
+ if (validationErrors.length > 0 && this.options.onValidationError) {
120
+ this.options.onValidationError(validationErrors);
121
+ }
122
+ }
123
+ if (networkError) {
124
+ console.error(`[Network error]: ${networkError}`);
125
+ // Check if network error is related to expired signature
126
+ if (networkError.message && networkError.message.includes('Signature has expired')) {
127
+ console.log('==== JWT NETWORK ERROR DETECTED ====', networkError.message);
128
+ // Try to refresh the token if a refresh function is provided
129
+ if (this.options.refreshToken && !this.isRefreshing) {
130
+ this.isRefreshing = true;
131
+ // Attempt to refresh the token
132
+ this.options.refreshToken()
133
+ .then(newToken => {
134
+ var _a, _b;
135
+ if (newToken) {
136
+ console.log('==== TOKEN REFRESHED (NETWORK) ====');
137
+ // Set the new token
138
+ this.setToken(newToken);
139
+ // Retry the failed request
140
+ const oldHeaders = operation.getContext().headers;
141
+ operation.setContext({
142
+ headers: {
143
+ ...oldHeaders,
144
+ authorization: `JWT ${newToken}`,
145
+ },
146
+ });
147
+ // Retry the operation
148
+ return forward(operation);
149
+ }
150
+ else {
151
+ console.log('==== TOKEN REFRESH FAILED (NETWORK) ====');
152
+ // If refresh token failed, call onUnauthorized
153
+ (_b = (_a = this.options).onUnauthorized) === null || _b === void 0 ? void 0 : _b.call(_a);
154
+ }
155
+ })
156
+ .catch(error => {
157
+ var _a, _b;
158
+ console.error('Token refresh failed:', error);
159
+ (_b = (_a = this.options).onUnauthorized) === null || _b === void 0 ? void 0 : _b.call(_a);
160
+ })
161
+ .finally(() => {
162
+ this.isRefreshing = false;
163
+ });
164
+ }
165
+ else {
166
+ // If no refresh function is provided, just call onUnauthorized
167
+ (_b = (_a = this.options).onUnauthorized) === null || _b === void 0 ? void 0 : _b.call(_a);
168
+ }
169
+ }
170
+ // For server errors (400/500), try to extract more information
171
+ const serverError = networkError;
172
+ if (serverError.name === 'ServerError' &&
173
+ serverError.result &&
174
+ typeof serverError.result === 'object') {
175
+ // Handle validation errors that might be in the response body
176
+ try {
177
+ const errorResult = serverError.result;
178
+ if (errorResult.errors && Array.isArray(errorResult.errors)) {
179
+ const validationErrors = [];
180
+ errorResult.errors.forEach((error) => {
181
+ if (error.message && (error.message.includes('got invalid value') ||
182
+ (error.message.includes('Field') && error.message.includes('was not provided')))) {
183
+ const fieldMatch = error.message.match(/Field '([^']+)'/);
184
+ if (fieldMatch && fieldMatch[1]) {
185
+ validationErrors.push({
186
+ field: fieldMatch[1],
187
+ message: error.message.trim()
188
+ });
189
+ }
190
+ }
191
+ });
192
+ if (validationErrors.length > 0 && this.options.onValidationError) {
193
+ this.options.onValidationError(validationErrors);
194
+ }
195
+ }
196
+ }
197
+ catch (e) {
198
+ console.error('Error parsing server error response:', e);
199
+ }
200
+ }
201
+ (_d = (_c = this.options).onError) === null || _d === void 0 ? void 0 : _d.call(_c, networkError);
202
+ }
203
+ });
204
+ // Combine the links
205
+ const link = client_1.ApolloLink.from([errorLink, authLink, httpLink]);
206
+ // Create the Apollo Client
207
+ return new client_1.ApolloClient({
208
+ link,
209
+ cache: new client_1.InMemoryCache(),
210
+ defaultOptions: {
211
+ watchQuery: {
212
+ fetchPolicy: 'network-only',
213
+ errorPolicy: 'all',
214
+ },
215
+ query: {
216
+ fetchPolicy: 'network-only',
217
+ errorPolicy: 'all',
218
+ },
219
+ mutate: {
220
+ errorPolicy: 'all',
221
+ },
222
+ },
223
+ });
224
+ }
225
+ // Method to update the token
226
+ setToken(token) {
227
+ this.options.token = token;
228
+ // Recreate the client with the new token
229
+ this.apolloClient = this.createApolloClient();
230
+ }
231
+ // Get the Apollo client instance
232
+ getClient() {
233
+ return this.apolloClient;
234
+ }
235
+ // Get the base URL
236
+ getBaseUrl() {
237
+ return this.options.baseUrl;
238
+ }
239
+ // Refresh token - attempt to get a new token
240
+ async refreshToken() {
241
+ if (this.options.refreshToken) {
242
+ try {
243
+ const newToken = await this.options.refreshToken();
244
+ if (newToken) {
245
+ this.setToken(newToken);
246
+ return true;
247
+ }
248
+ }
249
+ catch (error) {
250
+ console.error('Failed to refresh token:', error);
251
+ }
252
+ }
253
+ return false;
254
+ }
255
+ // Logout - clear the token
256
+ logout() {
257
+ this.options.token = undefined;
258
+ this.apolloClient = this.createApolloClient();
259
+ this.apolloClient.resetStore();
260
+ }
261
+ }
262
+ exports.CloudForgeClient = CloudForgeClient;