@karpeleslab/klbfw 0.1.13 → 0.2.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/internal.js CHANGED
@@ -1,88 +1,166 @@
1
1
  'use strict';
2
- const fwWrapper = require('./fw-wrapper');
2
+ /**
3
+ * @fileoverview Internal helpers for the KLB Frontend Framework
4
+ *
5
+ * This module provides internal utility functions for REST API interactions,
6
+ * timezone handling, and response parsing.
7
+ */
3
8
 
4
- // vim: et:ts=4:sw=4
9
+ const fwWrapper = require('./fw-wrapper');
5
10
 
6
- function get_tz_pad(number, length) {
7
- var str = "" + number;
8
- while (str.length < length)
11
+ /**
12
+ * Pads a number with leading zeros
13
+ * @param {number} number - The number to pad
14
+ * @param {number} length - The desired length of the result
15
+ * @returns {string} The padded number
16
+ */
17
+ const padNumber = (number, length) => {
18
+ let str = String(number);
19
+ while (str.length < length) {
9
20
  str = '0' + str;
21
+ }
10
22
  return str;
11
- }
12
-
13
- function get_timezone_data() {
14
- // grab current offset value & built string
15
- var offset = new Date().getTimezoneOffset();
16
- offset = ((offset < 0 ? '+' : '-') + // Note the reversed sign!
17
- get_tz_pad(parseInt(Math.abs(offset / 60)), 2) +
18
- get_tz_pad(Math.abs(offset % 60), 2));
19
-
20
- // check if we have intl info
21
-
22
- if (typeof Intl != 'undefined' && (Intl.DateTimeFormat != undefined)) {
23
- return Intl.DateTimeFormat().resolvedOptions().timeZone + ";" + offset;
23
+ };
24
+
25
+ /**
26
+ * Gets timezone data in a format suitable for API calls
27
+ * @returns {string} Formatted timezone string
28
+ */
29
+ const getTimezoneData = () => {
30
+ // Grab current offset value & build string
31
+ const offset = new Date().getTimezoneOffset();
32
+ const sign = offset < 0 ? '+' : '-'; // Note the reversed sign!
33
+ const formattedOffset = sign +
34
+ padNumber(parseInt(Math.abs(offset / 60)), 2) +
35
+ padNumber(Math.abs(offset % 60), 2);
36
+
37
+ // Check if we have Intl info
38
+ if (typeof Intl !== 'undefined' && Intl.DateTimeFormat !== undefined) {
39
+ return Intl.DateTimeFormat().resolvedOptions().timeZone + ";" + formattedOffset;
24
40
  }
25
41
 
26
- return offset;
27
- }
28
-
29
- function rest_url(path, with_token, context) {
30
- if (!with_token) {
31
- if (fwWrapper.getCallUrlPrefix()) return fwWrapper.getCallUrlPrefix() + "/_special/rest/" + path;
32
- return "/_special/rest/" + path;
42
+ return formattedOffset;
43
+ };
44
+
45
+ /**
46
+ * Constructs a REST API URL
47
+ * @param {string} path - API endpoint path
48
+ * @param {boolean} withToken - Whether to include authentication token
49
+ * @param {Object} context - Context object with additional parameters
50
+ * @returns {string} Constructed URL
51
+ */
52
+ const buildRestUrl = (path, withToken, context) => {
53
+ if (!withToken) {
54
+ if (fwWrapper.getCallUrlPrefix()) return fwWrapper.getCallUrlPrefix() + "/_rest/" + path;
55
+ return "/_rest/" + path;
33
56
  }
57
+
34
58
  context = context || {};
35
- var glue = '?';
36
-
59
+ let glue = '?';
60
+
61
+ let callUrl;
37
62
  if (fwWrapper.getSiteStatic()) {
38
- var call_url = "/_special/rest/" + path + "?static";
63
+ callUrl = "/_rest/" + path + "?static";
39
64
  glue = '&';
40
65
  } else {
41
- var call_url = "/_special/rest/" + path;
66
+ callUrl = "/_rest/" + path;
67
+ }
68
+
69
+ if (fwWrapper.getCallUrlPrefix()) {
70
+ callUrl = fwWrapper.getCallUrlPrefix() + callUrl;
42
71
  }
43
- if (fwWrapper.getCallUrlPrefix()) call_url = fwWrapper.getCallUrlPrefix() + call_url;
44
72
 
45
- // copy context, proceed with overload then add to url
46
- var ctx_final = fwWrapper.getContext();
47
- for (var i in context) ctx_final[i] = context[i];
48
- for (var i in ctx_final) {
49
- if (i == "_") continue;
50
- call_url = call_url + glue + "_ctx[" + i + "]=" + encodeURIComponent(ctx_final[i]);
73
+ // Copy context, proceed with overload then add to url
74
+ const ctxFinal = fwWrapper.getContext();
75
+ for (const key in context) {
76
+ ctxFinal[key] = context[key];
77
+ }
78
+
79
+ for (const key in ctxFinal) {
80
+ if (key === "_") continue;
81
+ callUrl = callUrl + glue + "_ctx[" + key + "]=" + encodeURIComponent(ctxFinal[key]);
51
82
  glue = '&';
52
83
  }
53
- return call_url;
54
- }
84
+
85
+ return callUrl;
86
+ };
87
+
88
+ /**
89
+ * Checks if the environment supports required features
90
+ * @returns {boolean} Whether the environment is supported
91
+ */
92
+ const checkSupport = () => {
93
+ const missingFeatures = [];
94
+
95
+ if (typeof fetch === "undefined") {
96
+ missingFeatures.push("fetch API");
97
+ }
55
98
 
56
- function internal_rest(name, verb, params, context) {
99
+ if (!fwWrapper.supported()) {
100
+ missingFeatures.push("Framework wrapper");
101
+ }
102
+
103
+ if (missingFeatures.length > 0) {
104
+ console.error("Missing required features: " + missingFeatures.join(", "));
105
+ return false;
106
+ }
107
+
108
+ return true;
109
+ };
110
+
111
+ /**
112
+ * Makes an internal REST API call
113
+ * @param {string} name - API endpoint name
114
+ * @param {string} verb - HTTP method (GET, POST, etc.)
115
+ * @param {Object|string} params - Request parameters
116
+ * @param {Object} context - Context object with additional parameters
117
+ * @returns {Promise} Fetch promise
118
+ */
119
+ const internalRest = (name, verb, params, context) => {
57
120
  verb = verb || "GET";
58
121
  params = params || {};
59
122
  context = context || {};
60
123
 
61
124
  if (typeof window !== "undefined") {
62
- context['t'] = get_timezone_data();
125
+ context['t'] = getTimezoneData();
63
126
  }
64
- var call_url = rest_url(name, true, context);
65
-
66
- var headers = {};
67
- if (fwWrapper.getToken() != '') {
68
- headers['Authorization'] = 'Session '+fwWrapper.getToken();
127
+
128
+ const callUrl = buildRestUrl(name, true, context);
129
+ const headers = {};
130
+
131
+ if (fwWrapper.getToken() !== '') {
132
+ headers['Authorization'] = 'Session ' + fwWrapper.getToken();
69
133
  }
70
134
 
71
- if (verb == "GET") {
135
+ // Handle GET requests
136
+ if (verb === "GET") {
72
137
  if (params) {
73
- // check if params is a json string, or if it needs encoding
138
+ // Check if params is a JSON string, or if it needs encoding
74
139
  if (typeof params === "string") {
75
- call_url += "&_=" + encodeURIComponent(params);
140
+ return fetch(callUrl + "&_=" + encodeURIComponent(params), {
141
+ method: verb,
142
+ credentials: 'include',
143
+ headers: headers
144
+ });
76
145
  } else {
77
- call_url += "&_=" + encodeURIComponent(JSON.stringify(params));
146
+ return fetch(callUrl + "&_=" + encodeURIComponent(JSON.stringify(params)), {
147
+ method: verb,
148
+ credentials: 'include',
149
+ headers: headers
150
+ });
78
151
  }
79
152
  }
80
-
81
- return fetch(call_url, {method: verb, credentials: 'include', headers: headers});
153
+
154
+ return fetch(callUrl, {
155
+ method: verb,
156
+ credentials: 'include',
157
+ headers: headers
158
+ });
82
159
  }
83
160
 
84
- if ((FormData != undefined) && (params instanceof FormData)) {
85
- return fetch(call_url, {
161
+ // Handle FormData
162
+ if (typeof FormData !== "undefined" && (params instanceof FormData)) {
163
+ return fetch(callUrl, {
86
164
  method: verb,
87
165
  credentials: 'include',
88
166
  body: params,
@@ -90,70 +168,76 @@ function internal_rest(name, verb, params, context) {
90
168
  });
91
169
  }
92
170
 
171
+ // Handle JSON requests
93
172
  headers['Content-Type'] = 'application/json; charset=utf-8';
94
-
95
- return fetch(call_url, {
173
+
174
+ return fetch(callUrl, {
96
175
  method: verb,
97
176
  credentials: 'include',
98
177
  body: JSON.stringify(params),
99
178
  headers: headers
100
179
  });
101
- }
102
-
103
- function checkSupport() {
104
- var ok = true;
105
- if (!fetch) {
106
- console.error("Fetch unsupported");
107
- ok = false;
108
- }
109
-
110
- if (!fwWrapper.supported()) {
111
- console.error("FW not found");
112
- ok = false;
180
+ };
181
+
182
+ /**
183
+ * Parses API response and resolves/rejects accordingly
184
+ * @param {Response} response - Fetch Response object
185
+ * @param {Function} resolve - Promise resolve function
186
+ * @param {Function} reject - Promise reject function
187
+ */
188
+ const responseParse = (response, resolve, reject) => {
189
+ // Check if response is ok (status 200-299)
190
+ if (!response.ok) {
191
+ reject({
192
+ message: `HTTP Error: ${response.status} ${response.statusText}`,
193
+ status: response.status,
194
+ headers: response.headers
195
+ });
196
+ return;
113
197
  }
114
-
115
- return ok;
116
- }
117
-
118
- function responseParse(response, resolve, reject) {
119
- var contentType = response.headers.get("content-type");
120
- if (!contentType || contentType.indexOf("application/json") == -1) {
121
- response.text().then(
122
- function (text) {
123
- reject({message: "Not JSON", body: text, headers: response.headers});
124
- },
125
- reject
126
- ).catch(reject);
127
-
198
+
199
+ const contentType = response.headers.get("content-type");
200
+ if (!contentType || contentType.indexOf("application/json") === -1) {
201
+ response.text()
202
+ .then(text => {
203
+ reject({
204
+ message: "Not JSON",
205
+ body: text,
206
+ headers: response.headers
207
+ });
208
+ })
209
+ .catch(error => reject(error));
128
210
  return;
129
211
  }
130
212
 
131
- response.json().then(
132
- function (json) {
133
- // check for gtag
134
- if ((json.gtag) && (typeof window !== "undefined") && (window.gtag)) {
135
- json.gtag.map(function (item) { window.gtag.apply(null, item); });
213
+ response.json()
214
+ .then(json => {
215
+ // Check for gtag
216
+ if (json.gtag && typeof window !== "undefined" && window.gtag) {
217
+ json.gtag.map(item => window.gtag.apply(null, item));
136
218
  }
137
- // check for result
138
- if (json.result != "success" && json.result != "redirect") {
219
+
220
+ // Check for result
221
+ if (json.result !== "success" && json.result !== "redirect") {
139
222
  json.headers = response.headers;
140
223
  reject(json);
141
224
  } else {
142
225
  resolve(json);
143
226
  }
144
- },
145
- reject
146
- ).catch(reject)
147
- }
148
-
149
- module.exports.get_tz_pad = get_tz_pad;
150
-
151
- module.exports.get_timezone_data = get_timezone_data;
152
-
153
- module.exports.rest_url = rest_url;
154
-
155
- module.exports.internal_rest = internal_rest;
156
-
227
+ })
228
+ .catch(error => reject(error));
229
+ };
230
+
231
+ // Backward compatibility aliases
232
+ module.exports.get_tz_pad = padNumber;
233
+ module.exports.get_timezone_data = getTimezoneData;
234
+ module.exports.rest_url = buildRestUrl;
235
+ module.exports.internal_rest = internalRest;
157
236
  module.exports.checkSupport = checkSupport;
158
-
159
237
  module.exports.responseParse = responseParse;
238
+
239
+ // New exports with camelCase naming
240
+ module.exports.padNumber = padNumber;
241
+ module.exports.getTimezoneData = getTimezoneData;
242
+ module.exports.buildRestUrl = buildRestUrl;
243
+ module.exports.internalRest = internalRest;
package/package.json CHANGED
@@ -1,10 +1,20 @@
1
1
  {
2
2
  "name": "@karpeleslab/klbfw",
3
- "version": "0.1.13",
3
+ "version": "0.2.1",
4
4
  "description": "Frontend Framework",
5
5
  "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "files": [
8
+ "*.js",
9
+ "*.d.ts",
10
+ "LICENSE",
11
+ "README.md"
12
+ ],
6
13
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
14
+ "test": "jest",
15
+ "test:watch": "jest --watch",
16
+ "test:integration": "RUN_INTEGRATION_TESTS=true jest test/integration.test.js --env=node",
17
+ "test:coverage": "jest --coverage"
8
18
  },
9
19
  "repository": {
10
20
  "type": "git",
@@ -17,6 +27,21 @@
17
27
  },
18
28
  "homepage": "https://github.com/KarpelesLab/klbfw#readme",
19
29
  "dependencies": {
20
- "js-sha256": "^0.9.0"
30
+ "js-sha256": "^0.11.0"
31
+ },
32
+ "optionalDependencies": {
33
+ "node-fetch": "^2.7.0",
34
+ "xmldom": "^0.6.0"
35
+ },
36
+ "devDependencies": {
37
+ "jest": "^29.7.0",
38
+ "jest-environment-jsdom": "^29.7.0",
39
+ "node-fetch": "^2.7.0",
40
+ "xmldom": "^0.6.0"
41
+ },
42
+ "jest": {
43
+ "testEnvironment": "jsdom",
44
+ "collectCoverage": true,
45
+ "coverageDirectory": "coverage"
21
46
  }
22
47
  }
package/rest.js CHANGED
@@ -1,113 +1,161 @@
1
- 'use strict'
2
- // vim: et:ts=4:sw=4
1
+ 'use strict';
2
+ /**
3
+ * @fileoverview REST API client for KLB Frontend Framework
4
+ *
5
+ * This module provides functions for making REST API calls to KLB backend services.
6
+ */
3
7
 
4
8
  const internal = require('./internal');
5
9
  const fwWrapper = require('./fw-wrapper');
6
10
 
7
- module.exports.rest = (name, verb, params, context) => {
11
+ /**
12
+ * Handles platform-specific API calls
13
+ * @param {string} name - API endpoint name
14
+ * @param {string} verb - HTTP method (GET, POST, etc.)
15
+ * @param {Object} params - Request parameters
16
+ * @param {Object} context - Context object with additional parameters
17
+ * @returns {Promise} API response promise
18
+ */
19
+ const handlePlatformCall = (name, verb, params, context) => {
20
+ // For platform-specific REST implementations
8
21
  if (typeof __platformAsyncRest !== "undefined") {
9
22
  context = context || {};
10
- var ctx_final = fwWrapper.getContext();
11
- for (var i in context) ctx_final[i] = context[i];
12
- var p1 = new Promise(function(resolve, reject) {
13
- __platformAsyncRest(name, verb, params, ctx_final).then(function(result) {
14
- if (result.result != "success") {
15
- reject(result);
16
- } else {
17
- resolve(result);
18
- }
19
- }, reject);
23
+ const ctxFinal = fwWrapper.getContext();
24
+
25
+ // Merge context
26
+ for (const key in context) {
27
+ ctxFinal[key] = context[key];
28
+ }
29
+
30
+ return new Promise((resolve, reject) => {
31
+ __platformAsyncRest(name, verb, params, ctxFinal)
32
+ .then(result => {
33
+ if (result.result !== "success" && result.result !== "redirect") {
34
+ reject(result);
35
+ } else {
36
+ resolve(result);
37
+ }
38
+ })
39
+ .catch(error => {
40
+ reject(error || new Error('Unknown platform async error'));
41
+ });
20
42
  });
21
- return p1;
22
43
  }
44
+
45
+ // For legacy platform REST implementation
23
46
  if (typeof __platformRest !== "undefined") {
24
- // direct SSR-mode call to rest api
25
- return new Promise(function(resolve, reject) {
26
- __platformRest(name, verb, params, function(res, err) {
27
- if (err) {
28
- reject(err);
29
- } else if (res.result != "success") {
30
- reject(res);
31
- } else {
32
- resolve(res);
33
- }
47
+ return new Promise((resolve, reject) => {
48
+ __platformRest(name, verb, params, (res, err) => {
49
+ if (err) {
50
+ reject(err);
51
+ } else if (res.result !== "success") {
52
+ reject(res);
53
+ } else {
54
+ resolve(res);
55
+ }
56
+ });
34
57
  });
35
- });
36
58
  }
59
+
60
+ return null;
61
+ };
37
62
 
38
- if(!internal.checkSupport()) return;
63
+ /**
64
+ * Makes a REST API call
65
+ * @param {string} name - API endpoint name
66
+ * @param {string} verb - HTTP method (GET, POST, etc.)
67
+ * @param {Object} params - Request parameters
68
+ * @param {Object} context - Context object with additional parameters
69
+ * @returns {Promise} API response promise
70
+ */
71
+ const rest = (name, verb, params, context) => {
72
+ // Try platform-specific REST implementations first
73
+ const platformResult = handlePlatformCall(name, verb, params, context);
74
+ if (platformResult) {
75
+ return platformResult;
76
+ }
39
77
 
40
- return new Promise(function(resolve, reject) {
41
- var restResolved = function(data) {
42
- internal.responseParse(data, resolve, reject);
43
- }
78
+ // Fall back to standard fetch implementation
79
+ if (!internal.checkSupport()) {
80
+ return Promise.reject(new Error('Environment not supported'));
81
+ }
44
82
 
45
- var restRejected = function(data) {
83
+ return new Promise((resolve, reject) => {
84
+ const handleSuccess = data => {
85
+ internal.responseParse(data, resolve, reject);
86
+ };
87
+
88
+ const handleError = data => {
46
89
  reject(data);
47
- }
48
-
49
- var restCatch = function(data) {
50
- console.error(data);
51
- // TODO log errors
52
- }
53
-
54
-
55
- internal.internal_rest(name, verb, params, context)
56
- .then(restResolved, restRejected)
57
- .catch(restCatch)
90
+ };
91
+
92
+ const handleException = error => {
93
+ console.error(error);
94
+ // TODO: Add proper error logging
95
+ };
96
+
97
+ internal.internalRest(name, verb, params, context)
98
+ .then(handleSuccess, handleError)
99
+ .catch(handleException);
58
100
  });
59
101
  };
60
102
 
61
- module.exports.rest_get = (name, params) => {
62
- if (typeof __platformAsyncRest !== "undefined") {
63
- return __platformAsyncRest(name, "GET", params);
64
- }
65
- if (typeof __platformRest !== "undefined") {
66
- // direct SSR-mode call to rest api
67
- return new Promise(function(resolve, reject) {
68
- __platformRest(name, "GET", params, function(res, err) {
69
- if (err) {
70
- reject(err);
71
- } else if (res.result != "success") {
72
- reject(res);
73
- } else {
74
- resolve(res);
75
- }
76
- });
77
- });
103
+ /**
104
+ * Makes a GET request to the REST API
105
+ * @param {string} name - API endpoint name
106
+ * @param {Object} params - Request parameters
107
+ * @returns {Promise} API response promise
108
+ */
109
+ const restGet = (name, params) => {
110
+ // Try platform-specific REST implementations first
111
+ const platformResult = handlePlatformCall(name, "GET", params);
112
+ if (platformResult) {
113
+ return platformResult;
78
114
  }
79
115
 
80
- if(!internal.checkSupport()) return;
116
+ // Fall back to standard fetch implementation
117
+ if (!internal.checkSupport()) {
118
+ return Promise.reject(new Error('Environment not supported'));
119
+ }
81
120
 
82
121
  params = params || {};
83
- var call_url = internal.rest_url(name, false);
122
+ let callUrl = internal.buildRestUrl(name, false);
84
123
 
85
124
  if (params) {
86
- // check if params is a json string, or if it needs encoding
125
+ // Check if params is a JSON string, or if it needs encoding
87
126
  if (typeof params === "string") {
88
- call_url += "?_=" + encodeURIComponent(params);
127
+ callUrl += "?_=" + encodeURIComponent(params);
89
128
  } else {
90
- call_url += "?_=" + encodeURIComponent(JSON.stringify(params));
129
+ callUrl += "?_=" + encodeURIComponent(JSON.stringify(params));
91
130
  }
92
131
  }
93
132
 
94
- var restResolved = function(data) {
95
- internal.responseParse(data, resolve, reject);
96
- }
97
-
98
- var restRejected = function(data) {
99
- reject(data);
100
- }
101
-
102
- var restCatch = function(data) {
103
- console.error(data);
104
- // TODO log errors
105
- }
106
-
107
- return new Promise(function(resolve, reject) {
108
- fetch(call_url, {
133
+ return new Promise((resolve, reject) => {
134
+ const handleSuccess = data => {
135
+ internal.responseParse(data, resolve, reject);
136
+ };
137
+
138
+ const handleError = data => {
139
+ reject(data);
140
+ };
141
+
142
+ const handleException = error => {
143
+ console.error(error);
144
+ // TODO: Add proper error logging
145
+ };
146
+
147
+ fetch(callUrl, {
109
148
  method: 'GET',
110
149
  credentials: 'include'
111
- }).then(restResolved, restRejected).catch(restCatch);
150
+ })
151
+ .then(handleSuccess, handleError)
152
+ .catch(handleException);
112
153
  });
113
- }
154
+ };
155
+
156
+ // Export new camelCase API
157
+ module.exports.rest = rest;
158
+ module.exports.restGet = restGet;
159
+
160
+ // Backward compatibility
161
+ module.exports.rest_get = restGet;