@trackunit/irisx-proxy 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.
package/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # Trackunit proxy
2
+
3
+ The `@trackunit/irisx-proxy` package is the home of Trackunit's public hook used for calling IrisX Proxy.
4
+
5
+ This library is exposed publicly for use in the the Trackunit [Iris App SDK](https://www.npmjs.com/package/@trackunit/iris-app).
6
+
7
+ To browse all avaliable components visit our [Public Storybook](https://apps.iris.trackunit.com/storybook/).
8
+
9
+ For more info and a full guide on Iris App SDK Development, please visit our [Developer Hub](https://developers.trackunit.com/).
10
+
11
+ ## Development
12
+
13
+ At this point this library is only developed by Trackunit Employees.
14
+ For development related information see the [development readme](https://github.com/Trackunit/manager/blob/master/libs/react/components/DEVELOPMENT.md).
15
+
16
+ ## Trackunit
17
+
18
+ This package was developed by Trackunit ApS.
19
+ Trackunit is the leading SaaS-based IoT solution for the construction industry, offering an ecosystem of hardware, fleet management software & telematics.
20
+
21
+ ![The Trackunit logo](https://trackunit.com/wp-content/uploads/2022/03/top-logo.svg)
package/codegen.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { type CodegenConfig } from "@graphql-codegen/cli";
2
+ declare const config: CodegenConfig;
3
+ export default config;
package/index.cjs.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./src/index";
package/index.cjs.js ADDED
@@ -0,0 +1,317 @@
1
+ 'use strict';
2
+
3
+ var zod = require('zod');
4
+ var client = require('@apollo/client');
5
+ var react = require('react');
6
+
7
+ /**
8
+ * Schema for HTTP headers
9
+ *
10
+ * @property {string} name - The name of the header
11
+ * @property {string} value - The value of the header
12
+ */
13
+ const headerSchema = zod.z.object({
14
+ name: zod.z.string(),
15
+ value: zod.z.string(),
16
+ });
17
+ /**
18
+ * Schema for API call parameters
19
+ *
20
+ * @property {string} url - The URL for the API call (must be a valid URL)
21
+ * @property {('GET'|'POST'|'PUT'|'PATCH'|'DELETE')} method - The HTTP method for the API call
22
+ * @property {HeaderSchema[]} headers - An array of headers for the API call
23
+ * @property {string} [body] - Optional body content for the API call
24
+ */
25
+ const irisAppProxySchema = zod.z.object({
26
+ url: zod.z.string().url(),
27
+ method: zod.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
28
+ headers: zod.z.array(headerSchema),
29
+ body: zod.z.string().optional(),
30
+ });
31
+
32
+ const IrisAppProxyFetchDocument = {
33
+ kind: "Document",
34
+ definitions: [
35
+ {
36
+ kind: "OperationDefinition",
37
+ operation: "mutation",
38
+ name: { kind: "Name", value: "IrisAppProxyFetch" },
39
+ variableDefinitions: [
40
+ {
41
+ kind: "VariableDefinition",
42
+ variable: { kind: "Variable", name: { kind: "Name", value: "url" } },
43
+ type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "String" } } },
44
+ },
45
+ {
46
+ kind: "VariableDefinition",
47
+ variable: { kind: "Variable", name: { kind: "Name", value: "method" } },
48
+ type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "HttpMethod" } } },
49
+ },
50
+ {
51
+ kind: "VariableDefinition",
52
+ variable: { kind: "Variable", name: { kind: "Name", value: "headers" } },
53
+ type: {
54
+ kind: "NonNullType",
55
+ type: {
56
+ kind: "ListType",
57
+ type: {
58
+ kind: "NonNullType",
59
+ type: { kind: "NamedType", name: { kind: "Name", value: "IrisAppProxyHeader" } },
60
+ },
61
+ },
62
+ },
63
+ },
64
+ {
65
+ kind: "VariableDefinition",
66
+ variable: { kind: "Variable", name: { kind: "Name", value: "body" } },
67
+ type: { kind: "NamedType", name: { kind: "Name", value: "String" } },
68
+ },
69
+ ],
70
+ selectionSet: {
71
+ kind: "SelectionSet",
72
+ selections: [
73
+ {
74
+ kind: "Field",
75
+ name: { kind: "Name", value: "irisAppProxyFetch" },
76
+ arguments: [
77
+ {
78
+ kind: "Argument",
79
+ name: { kind: "Name", value: "url" },
80
+ value: { kind: "Variable", name: { kind: "Name", value: "url" } },
81
+ },
82
+ {
83
+ kind: "Argument",
84
+ name: { kind: "Name", value: "method" },
85
+ value: { kind: "Variable", name: { kind: "Name", value: "method" } },
86
+ },
87
+ {
88
+ kind: "Argument",
89
+ name: { kind: "Name", value: "headers" },
90
+ value: { kind: "Variable", name: { kind: "Name", value: "headers" } },
91
+ },
92
+ {
93
+ kind: "Argument",
94
+ name: { kind: "Name", value: "body" },
95
+ value: { kind: "Variable", name: { kind: "Name", value: "body" } },
96
+ },
97
+ ],
98
+ selectionSet: {
99
+ kind: "SelectionSet",
100
+ selections: [
101
+ { kind: "Field", name: { kind: "Name", value: "success" } },
102
+ { kind: "Field", name: { kind: "Name", value: "body" } },
103
+ { kind: "Field", name: { kind: "Name", value: "status" } },
104
+ { kind: "Field", name: { kind: "Name", value: "errorMessage" } },
105
+ ],
106
+ },
107
+ },
108
+ ],
109
+ },
110
+ },
111
+ ],
112
+ };
113
+
114
+ /**
115
+ * Validates and parses the response body.
116
+ *
117
+ * @template T - The type inferred from the response schema.
118
+ * @param {string} responseBody - The base64-encoded response body from the API.
119
+ * @param {z.ZodType<T>} responseSchema - The Zod schema used to validate and parse the response data.
120
+ * @returns {T | null} The parsed and validated response data of type `T`, or `null` if validation fails.
121
+ */
122
+ const validateAndParseResponse = (responseBody, responseSchema) => {
123
+ const decodedBody = atob(responseBody);
124
+ const parsedBody = JSON.parse(decodedBody);
125
+ const validationResult = responseSchema.safeParse(parsedBody);
126
+ if (!validationResult.success) {
127
+ return { error: validationResult.error };
128
+ }
129
+ return { data: validationResult.data };
130
+ };
131
+
132
+ /**
133
+ * Hook for managing proxy interactions.
134
+ * This hook handles different layers of errors and responses from the provided endpoint through the IrisAppProxy.
135
+ * For the auth token, we recommend saving it using the IrisAppProxyStoreSecrets mutation and including it in the requestOptions as one of the headers.
136
+ * E.g. { name: "Authorization", value: `Bearer {{OAuthToken}}` }
137
+ *
138
+ * @param props - The properties for useIrisAppProxy hook.
139
+ * @returns An object containing chatbot state and functions.
140
+ */
141
+ const useIrisAppProxy = (props) => {
142
+ const { requestOptions, responseSchema, onSuccess, onError } = props;
143
+ const [error, setError] = react.useState(null);
144
+ const [proxyData, setProxyData] = react.useState(null);
145
+ const [mutate, { loading }] = client.useMutation(IrisAppProxyFetchDocument);
146
+ const createRequest = () => {
147
+ const request = {
148
+ ...requestOptions,
149
+ headers: [
150
+ { name: "Content-Type", value: "application/json" },
151
+ { name: "Accept-Profile", value: "default" },
152
+ ...requestOptions.headers,
153
+ ],
154
+ };
155
+ const result = irisAppProxySchema.safeParse(request);
156
+ if (!result.success) {
157
+ const localError = {
158
+ name: "Failed to parse request",
159
+ message: JSON.stringify(result.error, null, 2),
160
+ };
161
+ setError(localError);
162
+ onError && onError(localError);
163
+ return null;
164
+ }
165
+ return result.data;
166
+ };
167
+ const customMutate = async (request) => {
168
+ var _a, _b;
169
+ try {
170
+ const proxyRequest = request !== null && request !== void 0 ? request : createRequest();
171
+ if (!proxyRequest) {
172
+ return;
173
+ }
174
+ const { data, errors: gqlErrors } = await mutate({
175
+ variables: { ...proxyRequest, body: proxyRequest.body },
176
+ });
177
+ // Handle GraphQL errors
178
+ if (gqlErrors) {
179
+ const localError = {
180
+ name: "GraphQL Error",
181
+ message: JSON.stringify(gqlErrors, null, 2),
182
+ };
183
+ setError(localError);
184
+ onError && onError(localError);
185
+ return;
186
+ }
187
+ // Handle errors from Proxy (These does not fail the GraphQL query)
188
+ if (data && !data.irisAppProxyFetch.success) {
189
+ const localError = {
190
+ name: "Error from Proxy Fetch",
191
+ message: JSON.stringify(JSON.parse((_a = data.irisAppProxyFetch.errorMessage) !== null && _a !== void 0 ? _a : ""), null, 2),
192
+ };
193
+ setError(localError);
194
+ onError && onError(localError);
195
+ return;
196
+ }
197
+ // Handle errors from external service (These does not fail the GraphQL query)
198
+ if (data && data.irisAppProxyFetch.success && data.irisAppProxyFetch.status !== 200) {
199
+ const decodedBody = atob((_b = data.irisAppProxyFetch.body) !== null && _b !== void 0 ? _b : "");
200
+ const localError = {
201
+ name: `Error from external service (${data.irisAppProxyFetch.status})`,
202
+ message: decodedBody,
203
+ };
204
+ setError(localError);
205
+ onError && onError(localError);
206
+ return;
207
+ }
208
+ // Handle successful response
209
+ if (data === null || data === void 0 ? void 0 : data.irisAppProxyFetch.body) {
210
+ const parsedBody = validateAndParseResponse(data.irisAppProxyFetch.body, responseSchema);
211
+ if (parsedBody.error) {
212
+ setError(parsedBody.error);
213
+ onError && onError(parsedBody.error);
214
+ return;
215
+ }
216
+ if (parsedBody.data) {
217
+ setProxyData(parsedBody.data);
218
+ onSuccess && onSuccess(parsedBody.data);
219
+ return parsedBody;
220
+ }
221
+ else {
222
+ return;
223
+ }
224
+ }
225
+ else {
226
+ return;
227
+ }
228
+ }
229
+ catch (error) {
230
+ const localError = {
231
+ name: "Error",
232
+ message: JSON.stringify(error, null, 2),
233
+ };
234
+ setError(localError);
235
+ onError && onError(localError);
236
+ return;
237
+ }
238
+ };
239
+ const convertHeadersToIrisAppProxySchema = (headers) => {
240
+ if (!headers) {
241
+ return [];
242
+ }
243
+ if (headers instanceof Headers) {
244
+ const headersArray = [];
245
+ headers.forEach((value, name) => {
246
+ headersArray.push({ name, value });
247
+ });
248
+ return headersArray;
249
+ }
250
+ if (Array.isArray(headers)) {
251
+ return headers.map(([name, value]) => ({ name, value: value.toString() }));
252
+ }
253
+ if (typeof headers === "object") {
254
+ return Object.entries(headers).map(([name, value]) => ({ name, value: value.toString() }));
255
+ }
256
+ return [];
257
+ };
258
+ const mapToIrisAppProxySchema = (input, init) => {
259
+ var _a;
260
+ const headersArray = convertHeadersToIrisAppProxySchema(init === null || init === void 0 ? void 0 : init.headers);
261
+ const result = irisAppProxySchema.safeParse({
262
+ url: input,
263
+ method: (_a = init === null || init === void 0 ? void 0 : init.method) !== null && _a !== void 0 ? _a : "GET",
264
+ headers: headersArray,
265
+ body: (init === null || init === void 0 ? void 0 : init.body) ? String(init.body) : undefined,
266
+ });
267
+ if (!result.success) {
268
+ const localError = {
269
+ name: "Failed to map request",
270
+ message: JSON.stringify(result.error, null, 2),
271
+ };
272
+ setError(localError);
273
+ onError && onError(localError);
274
+ return null;
275
+ }
276
+ return result.data;
277
+ };
278
+ /**
279
+ * Performs a proxy fetch operation using the IrisX API.
280
+ * Use this function when you need to have a function that mimics the browser fetch API.
281
+ * This could be when using an external library that requires a fetch function. *
282
+ *
283
+ * @async
284
+ * @function irisXProxyFetch
285
+ * @param {RequestInfo | URL} input - The resource that you want to fetch, which can be a URL string or a Request object.
286
+ * @param {RequestInit} [init] - Optional configuration options for the request, similar to the standard fetch API.
287
+ * @returns {Promise<Response | undefined>} A promise that resolves to a Response object or undefined if the request mapping fails.
288
+ * @description
289
+ * This function maps the provided request to the IrisApp proxy schema. If the mapping is successful, it initiates a custom mutation with the mapped request.
290
+ * If the mapping fails, the function returns without performing the fetch operation. The function handles errors and returns appropriate responses based on the mutation outcome.
291
+ */
292
+ const irisXProxyFetch = async (input, init) => {
293
+ const proxyRequest = mapToIrisAppProxySchema(input, init);
294
+ if (!proxyRequest) {
295
+ return;
296
+ }
297
+ const response = await customMutate(proxyRequest);
298
+ if (response === null || response === void 0 ? void 0 : response.error) {
299
+ return new Response(JSON.stringify(response.error), {
300
+ status: 500,
301
+ headers: { "Content-Type": "application/json" },
302
+ });
303
+ }
304
+ if (response === null || response === void 0 ? void 0 : response.data) {
305
+ return new Response(JSON.stringify(response.data), {
306
+ status: 200,
307
+ headers: { "Content-Type": "application/json" },
308
+ });
309
+ }
310
+ return new Response(null, { status: 204 });
311
+ };
312
+ return { data: proxyData, mutate: customMutate, loading, error, irisXProxyFetch };
313
+ };
314
+
315
+ exports.headerSchema = headerSchema;
316
+ exports.irisAppProxySchema = irisAppProxySchema;
317
+ exports.useIrisAppProxy = useIrisAppProxy;
package/index.esm.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./src/index";
package/index.esm.js ADDED
@@ -0,0 +1,313 @@
1
+ import { z } from 'zod';
2
+ import { useMutation } from '@apollo/client';
3
+ import { useState } from 'react';
4
+
5
+ /**
6
+ * Schema for HTTP headers
7
+ *
8
+ * @property {string} name - The name of the header
9
+ * @property {string} value - The value of the header
10
+ */
11
+ const headerSchema = z.object({
12
+ name: z.string(),
13
+ value: z.string(),
14
+ });
15
+ /**
16
+ * Schema for API call parameters
17
+ *
18
+ * @property {string} url - The URL for the API call (must be a valid URL)
19
+ * @property {('GET'|'POST'|'PUT'|'PATCH'|'DELETE')} method - The HTTP method for the API call
20
+ * @property {HeaderSchema[]} headers - An array of headers for the API call
21
+ * @property {string} [body] - Optional body content for the API call
22
+ */
23
+ const irisAppProxySchema = z.object({
24
+ url: z.string().url(),
25
+ method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
26
+ headers: z.array(headerSchema),
27
+ body: z.string().optional(),
28
+ });
29
+
30
+ const IrisAppProxyFetchDocument = {
31
+ kind: "Document",
32
+ definitions: [
33
+ {
34
+ kind: "OperationDefinition",
35
+ operation: "mutation",
36
+ name: { kind: "Name", value: "IrisAppProxyFetch" },
37
+ variableDefinitions: [
38
+ {
39
+ kind: "VariableDefinition",
40
+ variable: { kind: "Variable", name: { kind: "Name", value: "url" } },
41
+ type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "String" } } },
42
+ },
43
+ {
44
+ kind: "VariableDefinition",
45
+ variable: { kind: "Variable", name: { kind: "Name", value: "method" } },
46
+ type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "HttpMethod" } } },
47
+ },
48
+ {
49
+ kind: "VariableDefinition",
50
+ variable: { kind: "Variable", name: { kind: "Name", value: "headers" } },
51
+ type: {
52
+ kind: "NonNullType",
53
+ type: {
54
+ kind: "ListType",
55
+ type: {
56
+ kind: "NonNullType",
57
+ type: { kind: "NamedType", name: { kind: "Name", value: "IrisAppProxyHeader" } },
58
+ },
59
+ },
60
+ },
61
+ },
62
+ {
63
+ kind: "VariableDefinition",
64
+ variable: { kind: "Variable", name: { kind: "Name", value: "body" } },
65
+ type: { kind: "NamedType", name: { kind: "Name", value: "String" } },
66
+ },
67
+ ],
68
+ selectionSet: {
69
+ kind: "SelectionSet",
70
+ selections: [
71
+ {
72
+ kind: "Field",
73
+ name: { kind: "Name", value: "irisAppProxyFetch" },
74
+ arguments: [
75
+ {
76
+ kind: "Argument",
77
+ name: { kind: "Name", value: "url" },
78
+ value: { kind: "Variable", name: { kind: "Name", value: "url" } },
79
+ },
80
+ {
81
+ kind: "Argument",
82
+ name: { kind: "Name", value: "method" },
83
+ value: { kind: "Variable", name: { kind: "Name", value: "method" } },
84
+ },
85
+ {
86
+ kind: "Argument",
87
+ name: { kind: "Name", value: "headers" },
88
+ value: { kind: "Variable", name: { kind: "Name", value: "headers" } },
89
+ },
90
+ {
91
+ kind: "Argument",
92
+ name: { kind: "Name", value: "body" },
93
+ value: { kind: "Variable", name: { kind: "Name", value: "body" } },
94
+ },
95
+ ],
96
+ selectionSet: {
97
+ kind: "SelectionSet",
98
+ selections: [
99
+ { kind: "Field", name: { kind: "Name", value: "success" } },
100
+ { kind: "Field", name: { kind: "Name", value: "body" } },
101
+ { kind: "Field", name: { kind: "Name", value: "status" } },
102
+ { kind: "Field", name: { kind: "Name", value: "errorMessage" } },
103
+ ],
104
+ },
105
+ },
106
+ ],
107
+ },
108
+ },
109
+ ],
110
+ };
111
+
112
+ /**
113
+ * Validates and parses the response body.
114
+ *
115
+ * @template T - The type inferred from the response schema.
116
+ * @param {string} responseBody - The base64-encoded response body from the API.
117
+ * @param {z.ZodType<T>} responseSchema - The Zod schema used to validate and parse the response data.
118
+ * @returns {T | null} The parsed and validated response data of type `T`, or `null` if validation fails.
119
+ */
120
+ const validateAndParseResponse = (responseBody, responseSchema) => {
121
+ const decodedBody = atob(responseBody);
122
+ const parsedBody = JSON.parse(decodedBody);
123
+ const validationResult = responseSchema.safeParse(parsedBody);
124
+ if (!validationResult.success) {
125
+ return { error: validationResult.error };
126
+ }
127
+ return { data: validationResult.data };
128
+ };
129
+
130
+ /**
131
+ * Hook for managing proxy interactions.
132
+ * This hook handles different layers of errors and responses from the provided endpoint through the IrisAppProxy.
133
+ * For the auth token, we recommend saving it using the IrisAppProxyStoreSecrets mutation and including it in the requestOptions as one of the headers.
134
+ * E.g. { name: "Authorization", value: `Bearer {{OAuthToken}}` }
135
+ *
136
+ * @param props - The properties for useIrisAppProxy hook.
137
+ * @returns An object containing chatbot state and functions.
138
+ */
139
+ const useIrisAppProxy = (props) => {
140
+ const { requestOptions, responseSchema, onSuccess, onError } = props;
141
+ const [error, setError] = useState(null);
142
+ const [proxyData, setProxyData] = useState(null);
143
+ const [mutate, { loading }] = useMutation(IrisAppProxyFetchDocument);
144
+ const createRequest = () => {
145
+ const request = {
146
+ ...requestOptions,
147
+ headers: [
148
+ { name: "Content-Type", value: "application/json" },
149
+ { name: "Accept-Profile", value: "default" },
150
+ ...requestOptions.headers,
151
+ ],
152
+ };
153
+ const result = irisAppProxySchema.safeParse(request);
154
+ if (!result.success) {
155
+ const localError = {
156
+ name: "Failed to parse request",
157
+ message: JSON.stringify(result.error, null, 2),
158
+ };
159
+ setError(localError);
160
+ onError && onError(localError);
161
+ return null;
162
+ }
163
+ return result.data;
164
+ };
165
+ const customMutate = async (request) => {
166
+ var _a, _b;
167
+ try {
168
+ const proxyRequest = request !== null && request !== void 0 ? request : createRequest();
169
+ if (!proxyRequest) {
170
+ return;
171
+ }
172
+ const { data, errors: gqlErrors } = await mutate({
173
+ variables: { ...proxyRequest, body: proxyRequest.body },
174
+ });
175
+ // Handle GraphQL errors
176
+ if (gqlErrors) {
177
+ const localError = {
178
+ name: "GraphQL Error",
179
+ message: JSON.stringify(gqlErrors, null, 2),
180
+ };
181
+ setError(localError);
182
+ onError && onError(localError);
183
+ return;
184
+ }
185
+ // Handle errors from Proxy (These does not fail the GraphQL query)
186
+ if (data && !data.irisAppProxyFetch.success) {
187
+ const localError = {
188
+ name: "Error from Proxy Fetch",
189
+ message: JSON.stringify(JSON.parse((_a = data.irisAppProxyFetch.errorMessage) !== null && _a !== void 0 ? _a : ""), null, 2),
190
+ };
191
+ setError(localError);
192
+ onError && onError(localError);
193
+ return;
194
+ }
195
+ // Handle errors from external service (These does not fail the GraphQL query)
196
+ if (data && data.irisAppProxyFetch.success && data.irisAppProxyFetch.status !== 200) {
197
+ const decodedBody = atob((_b = data.irisAppProxyFetch.body) !== null && _b !== void 0 ? _b : "");
198
+ const localError = {
199
+ name: `Error from external service (${data.irisAppProxyFetch.status})`,
200
+ message: decodedBody,
201
+ };
202
+ setError(localError);
203
+ onError && onError(localError);
204
+ return;
205
+ }
206
+ // Handle successful response
207
+ if (data === null || data === void 0 ? void 0 : data.irisAppProxyFetch.body) {
208
+ const parsedBody = validateAndParseResponse(data.irisAppProxyFetch.body, responseSchema);
209
+ if (parsedBody.error) {
210
+ setError(parsedBody.error);
211
+ onError && onError(parsedBody.error);
212
+ return;
213
+ }
214
+ if (parsedBody.data) {
215
+ setProxyData(parsedBody.data);
216
+ onSuccess && onSuccess(parsedBody.data);
217
+ return parsedBody;
218
+ }
219
+ else {
220
+ return;
221
+ }
222
+ }
223
+ else {
224
+ return;
225
+ }
226
+ }
227
+ catch (error) {
228
+ const localError = {
229
+ name: "Error",
230
+ message: JSON.stringify(error, null, 2),
231
+ };
232
+ setError(localError);
233
+ onError && onError(localError);
234
+ return;
235
+ }
236
+ };
237
+ const convertHeadersToIrisAppProxySchema = (headers) => {
238
+ if (!headers) {
239
+ return [];
240
+ }
241
+ if (headers instanceof Headers) {
242
+ const headersArray = [];
243
+ headers.forEach((value, name) => {
244
+ headersArray.push({ name, value });
245
+ });
246
+ return headersArray;
247
+ }
248
+ if (Array.isArray(headers)) {
249
+ return headers.map(([name, value]) => ({ name, value: value.toString() }));
250
+ }
251
+ if (typeof headers === "object") {
252
+ return Object.entries(headers).map(([name, value]) => ({ name, value: value.toString() }));
253
+ }
254
+ return [];
255
+ };
256
+ const mapToIrisAppProxySchema = (input, init) => {
257
+ var _a;
258
+ const headersArray = convertHeadersToIrisAppProxySchema(init === null || init === void 0 ? void 0 : init.headers);
259
+ const result = irisAppProxySchema.safeParse({
260
+ url: input,
261
+ method: (_a = init === null || init === void 0 ? void 0 : init.method) !== null && _a !== void 0 ? _a : "GET",
262
+ headers: headersArray,
263
+ body: (init === null || init === void 0 ? void 0 : init.body) ? String(init.body) : undefined,
264
+ });
265
+ if (!result.success) {
266
+ const localError = {
267
+ name: "Failed to map request",
268
+ message: JSON.stringify(result.error, null, 2),
269
+ };
270
+ setError(localError);
271
+ onError && onError(localError);
272
+ return null;
273
+ }
274
+ return result.data;
275
+ };
276
+ /**
277
+ * Performs a proxy fetch operation using the IrisX API.
278
+ * Use this function when you need to have a function that mimics the browser fetch API.
279
+ * This could be when using an external library that requires a fetch function. *
280
+ *
281
+ * @async
282
+ * @function irisXProxyFetch
283
+ * @param {RequestInfo | URL} input - The resource that you want to fetch, which can be a URL string or a Request object.
284
+ * @param {RequestInit} [init] - Optional configuration options for the request, similar to the standard fetch API.
285
+ * @returns {Promise<Response | undefined>} A promise that resolves to a Response object or undefined if the request mapping fails.
286
+ * @description
287
+ * This function maps the provided request to the IrisApp proxy schema. If the mapping is successful, it initiates a custom mutation with the mapped request.
288
+ * If the mapping fails, the function returns without performing the fetch operation. The function handles errors and returns appropriate responses based on the mutation outcome.
289
+ */
290
+ const irisXProxyFetch = async (input, init) => {
291
+ const proxyRequest = mapToIrisAppProxySchema(input, init);
292
+ if (!proxyRequest) {
293
+ return;
294
+ }
295
+ const response = await customMutate(proxyRequest);
296
+ if (response === null || response === void 0 ? void 0 : response.error) {
297
+ return new Response(JSON.stringify(response.error), {
298
+ status: 500,
299
+ headers: { "Content-Type": "application/json" },
300
+ });
301
+ }
302
+ if (response === null || response === void 0 ? void 0 : response.data) {
303
+ return new Response(JSON.stringify(response.data), {
304
+ status: 200,
305
+ headers: { "Content-Type": "application/json" },
306
+ });
307
+ }
308
+ return new Response(null, { status: 204 });
309
+ };
310
+ return { data: proxyData, mutate: customMutate, loading, error, irisXProxyFetch };
311
+ };
312
+
313
+ export { headerSchema, irisAppProxySchema, useIrisAppProxy };
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@trackunit/irisx-proxy",
3
+ "version": "0.0.1",
4
+ "repository": "https://github.com/Trackunit/manager",
5
+ "license": "SEE LICENSE IN LICENSE.txt",
6
+ "engines": {
7
+ "node": ">=20.x"
8
+ },
9
+ "dependencies": {
10
+ "react": "18.3.1",
11
+ "@apollo/client": "3.10.4",
12
+ "graphql": "^15.8.0",
13
+ "@graphql-codegen/cli": "^5.0.0",
14
+ "@graphql-typed-document-node/core": "^3.2.0",
15
+ "@trackunit/iris-app-api": "*",
16
+ "@trackunit/iris-app-build-utilities": "*",
17
+ "@trackunit/react-core-contexts-test": "*",
18
+ "zod": "3.22.4",
19
+ "jest-fetch-mock": "^3.0.3"
20
+ },
21
+ "module": "./index.esm.js",
22
+ "main": "./index.cjs.js",
23
+ "types": "./index.esm.d.ts"
24
+ }
@@ -0,0 +1,15 @@
1
+ import { ResultOf, DocumentTypeDecoration, TypedDocumentNode } from '@graphql-typed-document-node/core';
2
+ import { Incremental } from './graphql';
3
+ export type FragmentType<TDocumentType extends DocumentTypeDecoration<any, any>> = TDocumentType extends DocumentTypeDecoration<infer TType, any> ? [TType] extends [{
4
+ ' $fragmentName'?: infer TKey;
5
+ }] ? TKey extends string ? {
6
+ ' $fragmentRefs'?: {
7
+ [key in TKey]: TType;
8
+ };
9
+ } : never : never : never;
10
+ export declare function getFragmentData<TType>(_documentNode: DocumentTypeDecoration<TType, any>, fragmentType: FragmentType<DocumentTypeDecoration<TType, any>>): TType;
11
+ export declare function getFragmentData<TType>(_documentNode: DocumentTypeDecoration<TType, any>, fragmentType: FragmentType<DocumentTypeDecoration<TType, any>> | null | undefined): TType | null | undefined;
12
+ export declare function getFragmentData<TType>(_documentNode: DocumentTypeDecoration<TType, any>, fragmentType: ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>>): ReadonlyArray<TType>;
13
+ export declare function getFragmentData<TType>(_documentNode: DocumentTypeDecoration<TType, any>, fragmentType: ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>> | null | undefined): ReadonlyArray<TType> | null | undefined;
14
+ export declare function makeFragmentData<F extends DocumentTypeDecoration<any, any>, FT extends ResultOf<F>>(data: FT, _fragment: F): FragmentType<F>;
15
+ export declare function isFragmentReady<TQuery, TFrag>(queryNode: DocumentTypeDecoration<TQuery, any>, fragmentNode: TypedDocumentNode<TFrag>, data: FragmentType<TypedDocumentNode<Incremental<TFrag>, any>> | null | undefined): data is FragmentType<typeof fragmentNode>;
@@ -0,0 +1,39 @@
1
+ import * as types from './graphql';
2
+ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
3
+ /**
4
+ * Map of all GraphQL operations in the project.
5
+ *
6
+ * This map has several performance disadvantages:
7
+ * 1. It is not tree-shakeable, so it will include all operations in the project.
8
+ * 2. It is not minifiable, so the string of a GraphQL query will be multiple times inside the bundle.
9
+ * 3. It does not support dead code elimination, so it will add unused operations.
10
+ *
11
+ * Therefore it is highly recommended to use the babel or swc plugin for production.
12
+ */
13
+ declare const documents: {
14
+ "mutation IrisAppProxyFetch($url: String!, $method: HttpMethod!, $headers: [IrisAppProxyHeader!]!, $body: String) {\n irisAppProxyFetch(url: $url, method: $method, headers: $headers, body: $body) {\n success\n body\n status\n errorMessage\n }\n}": DocumentNode<types.IrisAppProxyFetchMutation, types.Exact<{
15
+ url: types.Scalars["String"]["input"];
16
+ method: types.HttpMethod;
17
+ headers: Array<types.IrisAppProxyHeader> | types.IrisAppProxyHeader;
18
+ body: types.InputMaybe<types.Scalars["String"]["input"]>;
19
+ }>>;
20
+ };
21
+ /**
22
+ * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
23
+ *
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const query = graphql(`query GetUser($id: ID!) { user(id: $id) { name } }`);
28
+ * ```
29
+ *
30
+ * The query argument is unknown!
31
+ * Please regenerate the types.
32
+ */
33
+ export declare function graphql(source: string): unknown;
34
+ /**
35
+ * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
36
+ */
37
+ export declare function graphql(source: "mutation IrisAppProxyFetch($url: String!, $method: HttpMethod!, $headers: [IrisAppProxyHeader!]!, $body: String) {\n irisAppProxyFetch(url: $url, method: $method, headers: $headers, body: $body) {\n success\n body\n status\n errorMessage\n }\n}"): (typeof documents)["mutation IrisAppProxyFetch($url: String!, $method: HttpMethod!, $headers: [IrisAppProxyHeader!]!, $body: String) {\n irisAppProxyFetch(url: $url, method: $method, headers: $headers, body: $body) {\n success\n body\n status\n errorMessage\n }\n}"];
38
+ export type DocumentType<TDocumentNode extends DocumentNode<any, any>> = TDocumentNode extends DocumentNode<infer TType, any> ? TType : never;
39
+ export {};
@@ -0,0 +1,162 @@
1
+ import { TypedDocumentNode as DocumentNode } from "@graphql-typed-document-node/core";
2
+ import type { PublicIrisAppManifest } from "@trackunit/iris-app-api";
3
+ export type Maybe<T> = T | null;
4
+ export type InputMaybe<T> = Maybe<T> | undefined;
5
+ export type Exact<T extends {
6
+ [key: string]: unknown;
7
+ }> = {
8
+ [K in keyof T]: T[K];
9
+ };
10
+ export type MakeOptional<T, K extends keyof T> = Omit<T, K> & {
11
+ [SubKey in K]?: Maybe<T[SubKey]>;
12
+ };
13
+ export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & {
14
+ [SubKey in K]: Maybe<T[SubKey]>;
15
+ };
16
+ export type MakeEmpty<T extends {
17
+ [key: string]: unknown;
18
+ }, K extends keyof T> = {
19
+ [_ in K]?: never;
20
+ };
21
+ export type Incremental<T> = T | {
22
+ [P in keyof T]?: P extends " $fragmentName" | "__typename" ? T[P] : never;
23
+ };
24
+ export type DateTimeISOString = string;
25
+ /** All built-in and custom scalars, mapped to their actual values */
26
+ export type Scalars = {
27
+ ID: {
28
+ input: string;
29
+ output: string;
30
+ };
31
+ String: {
32
+ input: string;
33
+ output: string;
34
+ };
35
+ Boolean: {
36
+ input: boolean;
37
+ output: boolean;
38
+ };
39
+ Int: {
40
+ input: number;
41
+ output: number;
42
+ };
43
+ Float: {
44
+ input: number;
45
+ output: number;
46
+ };
47
+ /** The ISO 4217 currency code of the monetary field definition. */
48
+ Currency: {
49
+ input: string;
50
+ output: string;
51
+ };
52
+ /** Cursor scalar. */
53
+ Cursor: {
54
+ input: string;
55
+ output: string;
56
+ };
57
+ /** Date without time, Eg. 2019-01-01 */
58
+ Date: {
59
+ input: DateTimeISOString;
60
+ output: DateTimeISOString;
61
+ };
62
+ /** DateTime represents a “date-time” as specified in section 5.6 of RFC 3339 */
63
+ DateTime: {
64
+ input: DateTimeISOString;
65
+ output: DateTimeISOString;
66
+ };
67
+ /** Duration represents an ISO 8601 duration */
68
+ Duration: {
69
+ input: any;
70
+ output: any;
71
+ };
72
+ /** Valid email address according to https://owasp.org/www-community/OWASP_Validation_Regex_Repository */
73
+ EmailAddress: {
74
+ input: string;
75
+ output: string;
76
+ };
77
+ /** A (possiblely multidimensional) set of coordinates following x, y, z order. */
78
+ GeoJSONCoordinates: {
79
+ input: number[] | number[][];
80
+ output: number[] | number[][];
81
+ };
82
+ Integer: {
83
+ input: any;
84
+ output: any;
85
+ };
86
+ /** Raw JSON object structure, not serialized to string */
87
+ JSON: {
88
+ input: any;
89
+ output: any;
90
+ };
91
+ /** The `JSONObject` scalar type represents JSON values as a string */
92
+ JSONObject: {
93
+ input: any;
94
+ output: any;
95
+ };
96
+ /** LocalDate scalar */
97
+ LocalDate: {
98
+ input: any;
99
+ output: any;
100
+ };
101
+ /** Javascript `Long`. Type represents a 64 bit integer. */
102
+ Long: {
103
+ input: any;
104
+ output: any;
105
+ };
106
+ /** Phone number in E.164 format */
107
+ PhoneNumber: {
108
+ input: string;
109
+ output: string;
110
+ };
111
+ /** The `PublicIrisAppManifest` scalar type represents an Iris App Manifest as JSON */
112
+ PublicIrisAppManifest: {
113
+ input: PublicIrisAppManifest;
114
+ output: PublicIrisAppManifest;
115
+ };
116
+ /** The `Upload` scalar type represents a file upload. */
117
+ Upload: {
118
+ input: any;
119
+ output: any;
120
+ };
121
+ /** Uniform resource locator */
122
+ Url: {
123
+ input: string;
124
+ output: string;
125
+ };
126
+ /** YearMonth scalar */
127
+ YearMonth: {
128
+ input: any;
129
+ output: any;
130
+ };
131
+ };
132
+ export declare const httpMethod: {
133
+ readonly DELETE: "DELETE";
134
+ readonly GET: "GET";
135
+ readonly PATCH: "PATCH";
136
+ readonly POST: "POST";
137
+ readonly PUT: "PUT";
138
+ };
139
+ export type HttpMethod = (typeof httpMethod)[keyof typeof httpMethod];
140
+ export type IrisAppProxyHeader = {
141
+ /** The name/key of the header */
142
+ name: Scalars["String"]["input"];
143
+ /** Use mustache template to insert secrets. Like `Bearer {{TOKEN}}` assuming the app has a stored secret containing `TOKEN` */
144
+ value: Scalars["String"]["input"];
145
+ };
146
+ export type IrisAppProxyFetchMutationVariables = Exact<{
147
+ url: Scalars["String"]["input"];
148
+ method: HttpMethod;
149
+ headers: Array<IrisAppProxyHeader> | IrisAppProxyHeader;
150
+ body: InputMaybe<Scalars["String"]["input"]>;
151
+ }>;
152
+ export type IrisAppProxyFetchMutation = {
153
+ __typename?: "Mutation";
154
+ irisAppProxyFetch: {
155
+ __typename?: "IrisAppProxyFetchResponse";
156
+ success: boolean;
157
+ body: string | null;
158
+ status: number | null;
159
+ errorMessage: string | null;
160
+ };
161
+ };
162
+ export declare const IrisAppProxyFetchDocument: DocumentNode<IrisAppProxyFetchMutation, IrisAppProxyFetchMutationVariables>;
@@ -0,0 +1,2 @@
1
+ export * from "./fragment-masking";
2
+ export * from "./gql";
@@ -0,0 +1,5 @@
1
+ import { DeepPartialNullable } from "@trackunit/react-core-contexts-test";
2
+ import * as gql from "./graphql";
3
+ export declare const mockForIrisAppProxyFetchMutation: (variables: gql.IrisAppProxyFetchMutationVariables, data?: DeepPartialNullable<gql.IrisAppProxyFetchMutation>) => import("@apollo/client/testing").MockedResponse<gql.IrisAppProxyFetchMutation, Record<string, any>> & {
4
+ data: gql.IrisAppProxyFetchMutation;
5
+ };
package/src/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./schema";
2
+ export * from "./useProxy";
@@ -0,0 +1,59 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Schema for HTTP headers
4
+ *
5
+ * @property {string} name - The name of the header
6
+ * @property {string} value - The value of the header
7
+ */
8
+ export declare const headerSchema: z.ZodObject<{
9
+ name: z.ZodString;
10
+ value: z.ZodString;
11
+ }, "strip", z.ZodTypeAny, {
12
+ name: string;
13
+ value: string;
14
+ }, {
15
+ name: string;
16
+ value: string;
17
+ }>;
18
+ export type HeaderSchema = z.infer<typeof headerSchema>;
19
+ /**
20
+ * Schema for API call parameters
21
+ *
22
+ * @property {string} url - The URL for the API call (must be a valid URL)
23
+ * @property {('GET'|'POST'|'PUT'|'PATCH'|'DELETE')} method - The HTTP method for the API call
24
+ * @property {HeaderSchema[]} headers - An array of headers for the API call
25
+ * @property {string} [body] - Optional body content for the API call
26
+ */
27
+ export declare const irisAppProxySchema: z.ZodObject<{
28
+ url: z.ZodString;
29
+ method: z.ZodEnum<["GET", "POST", "PUT", "PATCH", "DELETE"]>;
30
+ headers: z.ZodArray<z.ZodObject<{
31
+ name: z.ZodString;
32
+ value: z.ZodString;
33
+ }, "strip", z.ZodTypeAny, {
34
+ name: string;
35
+ value: string;
36
+ }, {
37
+ name: string;
38
+ value: string;
39
+ }>, "many">;
40
+ body: z.ZodOptional<z.ZodString>;
41
+ }, "strip", z.ZodTypeAny, {
42
+ url: string;
43
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
44
+ headers: {
45
+ name: string;
46
+ value: string;
47
+ }[];
48
+ body?: string | undefined;
49
+ }, {
50
+ url: string;
51
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
52
+ headers: {
53
+ name: string;
54
+ value: string;
55
+ }[];
56
+ body?: string | undefined;
57
+ }>;
58
+ export type IrisAppProxyRequestOptions = z.infer<typeof irisAppProxySchema>;
59
+ export type IrisAppProxyMethod = IrisAppProxyRequestOptions["method"];
@@ -0,0 +1,34 @@
1
+ import { z } from "zod";
2
+ import { IrisAppProxyRequestOptions } from "./schema";
3
+ export interface UseIrisAppProxyProps<R> {
4
+ requestOptions: IrisAppProxyRequestOptions;
5
+ responseSchema: z.ZodType<R>;
6
+ onSuccess?: (data: R) => void;
7
+ onError?: (error: {
8
+ name: string;
9
+ message: string;
10
+ }) => void;
11
+ }
12
+ export interface ProxyError {
13
+ name: string;
14
+ message: string;
15
+ }
16
+ /**
17
+ * Hook for managing proxy interactions.
18
+ * This hook handles different layers of errors and responses from the provided endpoint through the IrisAppProxy.
19
+ * For the auth token, we recommend saving it using the IrisAppProxyStoreSecrets mutation and including it in the requestOptions as one of the headers.
20
+ * E.g. { name: "Authorization", value: `Bearer {{OAuthToken}}` }
21
+ *
22
+ * @param props - The properties for useIrisAppProxy hook.
23
+ * @returns An object containing chatbot state and functions.
24
+ */
25
+ export declare const useIrisAppProxy: <R>(props: UseIrisAppProxyProps<R>) => {
26
+ data: R | null;
27
+ mutate: (request?: IrisAppProxyRequestOptions) => Promise<{
28
+ data?: R | undefined;
29
+ error?: ProxyError;
30
+ } | undefined>;
31
+ loading: boolean;
32
+ error: ProxyError | null;
33
+ irisXProxyFetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response | undefined>;
34
+ };
package/src/utils.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { z } from "zod";
2
+ import { ProxyError } from "./useProxy";
3
+ /**
4
+ * Validates and parses the response body.
5
+ *
6
+ * @template T - The type inferred from the response schema.
7
+ * @param {string} responseBody - The base64-encoded response body from the API.
8
+ * @param {z.ZodType<T>} responseSchema - The Zod schema used to validate and parse the response data.
9
+ * @returns {T | null} The parsed and validated response data of type `T`, or `null` if validation fails.
10
+ */
11
+ export declare const validateAndParseResponse: <T>(responseBody: string, responseSchema: z.ZodType<T>) => {
12
+ data?: T;
13
+ error?: ProxyError;
14
+ };
15
+ /**
16
+ * Encodes the given request body as a base64 string.
17
+ *
18
+ * @template R - The type of the request body.
19
+ * @param {R} body - The request body to be encoded.
20
+ * @returns {string} The base64-encoded string representation of the request body.
21
+ */
22
+ export declare const parseRequestBodyToBase64: <R>(body: R) => string;