ofsc-utility-browser 1.0.2 → 1.0.4
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/dist/activities/index.js +68 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +19 -0
- package/dist/oauthTokenService/index.js +27 -0
- package/dist/ofsc-utilities-min.js +1 -1
- package/dist/ofsc-utilities-min.js.map +1 -1
- package/dist/resources/index.js +132 -0
- package/dist/types.js +1 -0
- package/dist/users/index.d.ts +11 -0
- package/dist/users/index.js +59 -0
- package/dist/utilities/index.js +148 -0
- package/package.json +1 -1
- package/readme.md +13 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { fetchWithRetry, patchWithRetry } from "../utilities";
|
|
2
|
+
/**
|
|
3
|
+
* Fetches all resources from the OFSC instance.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} clientId - The OFSC client ID.
|
|
6
|
+
* @param {string} clientSecret - The OFSC client secret.
|
|
7
|
+
* @param {string} instanceUrl - The OFSC instance URL.
|
|
8
|
+
* @param {string} [initialToken] - The OAuth token to use. If not provided, a new token will be fetched.
|
|
9
|
+
*
|
|
10
|
+
* @returns {Promise<any[]>} A promise which resolves to an array of resource objects.
|
|
11
|
+
*/
|
|
12
|
+
export async function AllResources(clientId, clientSecret, instanceUrl, initialToken = "") {
|
|
13
|
+
/**
|
|
14
|
+
* The number of API calls made so far.
|
|
15
|
+
* Used to determine if we should wait 10 seconds to avoid server rate limits.
|
|
16
|
+
*/
|
|
17
|
+
let apiCallCount = 0;
|
|
18
|
+
/**
|
|
19
|
+
* The number of API calls after which we should wait 10 seconds.
|
|
20
|
+
*/
|
|
21
|
+
const WAIT_AFTER_CALLS = 20;
|
|
22
|
+
/**
|
|
23
|
+
* The delay in milliseconds to wait after reaching the API call limit.
|
|
24
|
+
*/
|
|
25
|
+
const DELAY_MS = 10000; // 10 seconds
|
|
26
|
+
/**
|
|
27
|
+
* Sleeps for the given amount of milliseconds.
|
|
28
|
+
*
|
|
29
|
+
* @param {number} ms - The amount of milliseconds to sleep.
|
|
30
|
+
* @returns {Promise<void>} A promise which resolves when the sleep is over.
|
|
31
|
+
*/
|
|
32
|
+
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
33
|
+
/**
|
|
34
|
+
* Fetches resources from the OFSC instance.
|
|
35
|
+
*
|
|
36
|
+
* @param {number} offset - The offset from which to fetch resources.
|
|
37
|
+
* @param {string} token - The OAuth token to use.
|
|
38
|
+
* @returns {Promise<ResourceResponse[]>} A promise which resolves to an array of resource responses.
|
|
39
|
+
*/
|
|
40
|
+
const fetchResources = async (offset, token) => {
|
|
41
|
+
apiCallCount++;
|
|
42
|
+
// Wait 10 seconds after 20 API calls to avoid server rate limits.
|
|
43
|
+
if (apiCallCount % WAIT_AFTER_CALLS === 0) {
|
|
44
|
+
console.warn(` Waiting 10 seconds after ${apiCallCount} API calls to avoid server rate limits.`);
|
|
45
|
+
await sleep(DELAY_MS);
|
|
46
|
+
}
|
|
47
|
+
const url = `https://${instanceUrl}.fs.ocs.oraclecloud.com/rest/ofscCore/v1/resources/?offset=${offset}&limit=100`;
|
|
48
|
+
const res = await fetchWithRetry(url, clientId, clientSecret, instanceUrl, token);
|
|
49
|
+
const { items, totalResults } = res.data;
|
|
50
|
+
const totalFetched = items.length;
|
|
51
|
+
console.log(`Received ${totalFetched} items (Total: ${offset + totalFetched})`);
|
|
52
|
+
if (offset + totalFetched >= totalResults) {
|
|
53
|
+
return items;
|
|
54
|
+
}
|
|
55
|
+
const nextItems = await fetchResources(offset + totalFetched, res.token);
|
|
56
|
+
return [...items, ...nextItems];
|
|
57
|
+
};
|
|
58
|
+
return fetchResources(0, initialToken);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Updates a resource with the given payload.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} clientId - The OFSC client ID.
|
|
64
|
+
* @param {string} clientSecret - The OFSC client secret.
|
|
65
|
+
* @param {string} instanceUrl - The OFSC instance URL.
|
|
66
|
+
* @param {string} resourceId - The ID of the resource to update.
|
|
67
|
+
* @param {any} payload - The data to update the resource with.
|
|
68
|
+
* @param {string} [token] - The OAuth token to use. If not provided, a new token will be fetched.
|
|
69
|
+
* @returns {Promise<any>} A promise which resolves to the updated resource data.
|
|
70
|
+
*/
|
|
71
|
+
export async function updateResource(clientId, clientSecret, instanceUrl, resourceId, payload, token = "") {
|
|
72
|
+
return patchWithRetry(`https://${instanceUrl}.fs.ocs.oraclecloud.com/rest/ofscCore/v1/resources/${resourceId}`, clientId, clientSecret, instanceUrl, token, payload);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Resets the email addresses of all resources to the given domain.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} clientId - The OFSC client ID.
|
|
78
|
+
* @param {string} clientSecret - The OFSC client secret.
|
|
79
|
+
* @param {string} instanceUrl - The OFSC instance URL.
|
|
80
|
+
* @param {string} [newdomain] - The new domain to update the resource emails to. Defaults to "noreply".
|
|
81
|
+
* @param {string} [token] - The OAuth token to use. If not provided, a new token will be fetched.
|
|
82
|
+
* @returns {Promise<any[]>} A promise which resolves to an array of objects containing the resource ID, original email, new email, and a comment.
|
|
83
|
+
*/
|
|
84
|
+
export async function resetResourcesEmail(clientId, clientSecret, instanceUrl, newdomain = "noreply.com", token = "") {
|
|
85
|
+
const resources = await AllResources(clientId, clientSecret, instanceUrl, token);
|
|
86
|
+
const resourcesWithEmails = resources.filter((resource) => resource.email);
|
|
87
|
+
const updatedResources = [];
|
|
88
|
+
const BATCH_SIZE = 200; // Update in batches of 200 to avoid server rate limits
|
|
89
|
+
const DELAY_MS = 10000; // Wait 10 seconds between batches
|
|
90
|
+
for (let i = 0; i < resourcesWithEmails.length; i += BATCH_SIZE) {
|
|
91
|
+
const batch = resourcesWithEmails.slice(i, i + BATCH_SIZE);
|
|
92
|
+
for (const resource of batch) {
|
|
93
|
+
console.log(`Total : ${resourcesWithEmails.length}`, `Total updated: ${updatedResources.length}`);
|
|
94
|
+
if (!resource.email)
|
|
95
|
+
continue;
|
|
96
|
+
if (!resource.resourceId) {
|
|
97
|
+
console.warn(`⚠️ Resource ID not found for ${JSON.stringify(resource)}`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
let email = resource.email;
|
|
101
|
+
if (!email.includes("@")) {
|
|
102
|
+
email = email.replace(newdomain, `@${newdomain}`);
|
|
103
|
+
}
|
|
104
|
+
if (email.includes(newdomain)) {
|
|
105
|
+
updatedResources.push({
|
|
106
|
+
id: resource.resourceId,
|
|
107
|
+
email: resource.email,
|
|
108
|
+
newEmail: "",
|
|
109
|
+
comment: "Email already updated"
|
|
110
|
+
});
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
email = resource.email.replace(/@.*/, `@${newdomain}`);
|
|
114
|
+
console.log(`Updating email for ${resource.resourceId} (${resource.email} -> ${email}) on ${instanceUrl}`);
|
|
115
|
+
const payload = {
|
|
116
|
+
email: email
|
|
117
|
+
};
|
|
118
|
+
const response = await updateResource(clientId, clientSecret, instanceUrl, resource.resourceId, payload, token);
|
|
119
|
+
token = response.token;
|
|
120
|
+
updatedResources.push({
|
|
121
|
+
id: response.data.resourceId,
|
|
122
|
+
email: resource.email,
|
|
123
|
+
newEmail: response.data.email
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (i + BATCH_SIZE < resourcesWithEmails.length) {
|
|
127
|
+
console.warn("⏳ Waiting 10 seconds before next batch...to avoid server rate limits.");
|
|
128
|
+
await new Promise(resolve => setTimeout(resolve, DELAY_MS));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return updatedResources;
|
|
132
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetches all users from the OFSC instance.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} clientId - The OFSC client ID.
|
|
5
|
+
* @param {string} clientSecret - The OFSC client secret.
|
|
6
|
+
* @param {string} instanceUrl - The OFSC instance URL.
|
|
7
|
+
* @param {string} [initialToken] - The OAuth token to use. If not provided, a new token will be fetched.
|
|
8
|
+
*
|
|
9
|
+
* @returns {Promise<any[]>} A promise which resolves to an array of resource objects.
|
|
10
|
+
*/
|
|
11
|
+
export declare function AllUsers(clientId: string, clientSecret: string, instanceUrl: string, initialToken?: string): Promise<any[]>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { fetchWithRetry } from "../utilities";
|
|
2
|
+
/**
|
|
3
|
+
* Fetches all users from the OFSC instance.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} clientId - The OFSC client ID.
|
|
6
|
+
* @param {string} clientSecret - The OFSC client secret.
|
|
7
|
+
* @param {string} instanceUrl - The OFSC instance URL.
|
|
8
|
+
* @param {string} [initialToken] - The OAuth token to use. If not provided, a new token will be fetched.
|
|
9
|
+
*
|
|
10
|
+
* @returns {Promise<any[]>} A promise which resolves to an array of resource objects.
|
|
11
|
+
*/
|
|
12
|
+
export async function AllUsers(clientId, clientSecret, instanceUrl, initialToken = "") {
|
|
13
|
+
/**
|
|
14
|
+
* The number of API calls made so far.
|
|
15
|
+
* Used to determine if we should wait 10 seconds to avoid server rate limits.
|
|
16
|
+
*/
|
|
17
|
+
let apiCallCount = 0;
|
|
18
|
+
/**
|
|
19
|
+
* The number of API calls after which we should wait 10 seconds.
|
|
20
|
+
*/
|
|
21
|
+
const WAIT_AFTER_CALLS = 20;
|
|
22
|
+
/**
|
|
23
|
+
* The delay in milliseconds to wait after reaching the API call limit.
|
|
24
|
+
*/
|
|
25
|
+
const DELAY_MS = 10000; // 10 seconds
|
|
26
|
+
/**
|
|
27
|
+
* Sleeps for the given amount of milliseconds.
|
|
28
|
+
*
|
|
29
|
+
* @param {number} ms - The amount of milliseconds to sleep.
|
|
30
|
+
* @returns {Promise<void>} A promise which resolves when the sleep is over.
|
|
31
|
+
*/
|
|
32
|
+
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
33
|
+
/**
|
|
34
|
+
* Fetches resources from the OFSC instance.
|
|
35
|
+
*
|
|
36
|
+
* @param {number} offset - The offset from which to fetch resources.
|
|
37
|
+
* @param {string} token - The OAuth token to use.
|
|
38
|
+
* @returns {Promise<ResourceResponse[]>} A promise which resolves to an array of resource responses.
|
|
39
|
+
*/
|
|
40
|
+
const fetchAllUsers = async (offset, token) => {
|
|
41
|
+
apiCallCount++;
|
|
42
|
+
// Wait 10 seconds after 20 API calls to avoid server rate limits.
|
|
43
|
+
if (apiCallCount % WAIT_AFTER_CALLS === 0) {
|
|
44
|
+
console.warn(` Waiting 10 seconds after ${apiCallCount} API calls to avoid server rate limits.`);
|
|
45
|
+
await sleep(DELAY_MS);
|
|
46
|
+
}
|
|
47
|
+
const url = `https://${instanceUrl}.fs.ocs.oraclecloud.com/rest/ofscCore/v1/users/?offset=${offset}&limit=100`;
|
|
48
|
+
const res = await fetchWithRetry(url, clientId, clientSecret, instanceUrl, token);
|
|
49
|
+
const { items, totalResults } = res.data;
|
|
50
|
+
const totalFetched = items.length;
|
|
51
|
+
console.log(`Received ${totalFetched} items (Total: ${offset + totalFetched})`);
|
|
52
|
+
if (offset + totalFetched >= totalResults) {
|
|
53
|
+
return items;
|
|
54
|
+
}
|
|
55
|
+
const nextItems = await fetchAllUsers(offset + totalFetched, res.token);
|
|
56
|
+
return [...items, ...nextItems];
|
|
57
|
+
};
|
|
58
|
+
return fetchAllUsers(0, initialToken);
|
|
59
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { DOMParser } from "xmldom";
|
|
2
|
+
import { getOAuthToken } from "../oauthTokenService";
|
|
3
|
+
/**
|
|
4
|
+
* Fetches a URL with retry logic for expired tokens.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} url - The URL to fetch.
|
|
7
|
+
* @param {string} clientId - The OFSC client ID.
|
|
8
|
+
* @param {string} clientSecret - The OFSC client secret.
|
|
9
|
+
* @param {string} instanceUrl - The OFSC instance URL.
|
|
10
|
+
* @param {string} token - The current OAuth token.
|
|
11
|
+
*
|
|
12
|
+
* @returns {Promise<{ data: any; token: string }>} A promise which resolves to an object containing the parsed JSON data and the latest OAuth token.
|
|
13
|
+
*/
|
|
14
|
+
export const patchWithRetry = async (url, clientId, clientSecret, instanceUrl, token, payload) => {
|
|
15
|
+
const doPost = async (bearer) => {
|
|
16
|
+
return fetch(url, {
|
|
17
|
+
method: "PATCH",
|
|
18
|
+
headers: {
|
|
19
|
+
Authorization: `Bearer ${bearer}`,
|
|
20
|
+
Accept: "application/json",
|
|
21
|
+
"Content-Type": "application/json"
|
|
22
|
+
},
|
|
23
|
+
body: JSON.stringify(payload)
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
let response = await doPost(token);
|
|
27
|
+
/* ---------- 401: refresh token ONCE per call ---------- */
|
|
28
|
+
if (response.status === 401) {
|
|
29
|
+
console.warn("⚠️ Token expired — renewing token…");
|
|
30
|
+
token = await getOAuthToken(clientId, clientSecret, instanceUrl);
|
|
31
|
+
response = await doPost(token);
|
|
32
|
+
}
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
data: await response.json(),
|
|
38
|
+
token
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
export const fetchWithRetry = async (url, clientId, clientSecret, instanceUrl, token, retries = 5, baseDelay = 500) => {
|
|
42
|
+
const doFetch = async (bearer) => {
|
|
43
|
+
return fetch(url, {
|
|
44
|
+
method: "GET",
|
|
45
|
+
headers: {
|
|
46
|
+
Authorization: `Bearer ${bearer}`,
|
|
47
|
+
Accept: "application/json"
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
// Try with the current token
|
|
52
|
+
let res = await doFetch(token);
|
|
53
|
+
/* ---------- 401: refresh token ONCE per call ---------- */
|
|
54
|
+
if (res.status === 401) {
|
|
55
|
+
console.warn("⚠️ Token expired — renewing token…");
|
|
56
|
+
token = await getOAuthToken(clientId, clientSecret, instanceUrl);
|
|
57
|
+
res = await doFetch(token);
|
|
58
|
+
}
|
|
59
|
+
/* ---------- 429: retry with backoff ---------- */
|
|
60
|
+
if (res.status === 429 && retries > 0) {
|
|
61
|
+
const retryAfter = res.headers.get("Retry-After");
|
|
62
|
+
console.log("⚠️ 429 received. Retrying...", retryAfter);
|
|
63
|
+
const delay = retryAfter ? Number(retryAfter) * 1000 : baseDelay;
|
|
64
|
+
console.warn(`⚠️ 429 received. Retrying in ${delay}ms... (${retries} left)`);
|
|
65
|
+
await new Promise(r => setTimeout(r, delay));
|
|
66
|
+
return fetchWithRetry(url, clientId, clientSecret, instanceUrl, token, retries - 1, baseDelay * 2);
|
|
67
|
+
}
|
|
68
|
+
// If still not OK → fail
|
|
69
|
+
if (!res.ok) {
|
|
70
|
+
const body = await res.text();
|
|
71
|
+
throw new Error(`❌ Request failed: ${res.status} ${res.statusText}\n${body}`);
|
|
72
|
+
}
|
|
73
|
+
// Return parsed JSON + latest token
|
|
74
|
+
return {
|
|
75
|
+
data: await res.json(),
|
|
76
|
+
token
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
export function stringCsv(rows) {
|
|
80
|
+
if (!rows || rows.length === 0) {
|
|
81
|
+
throw new Error("CSV creation failed: no rows provided.");
|
|
82
|
+
}
|
|
83
|
+
// Collect all unique headers from all rows
|
|
84
|
+
const headerSet = new Set();
|
|
85
|
+
rows.forEach(row => {
|
|
86
|
+
Object.keys(row).forEach(key => headerSet.add(key));
|
|
87
|
+
});
|
|
88
|
+
const headers = Array.from(headerSet);
|
|
89
|
+
// Build CSV content
|
|
90
|
+
const csvLines = [
|
|
91
|
+
headers.join(","), // header row
|
|
92
|
+
...rows.map(row => headers.map(h => escapeCsvValue(row[h])).join(","))
|
|
93
|
+
];
|
|
94
|
+
return csvLines.join("\n");
|
|
95
|
+
}
|
|
96
|
+
// Simple CSV escaping
|
|
97
|
+
function escapeCsvValue(value) {
|
|
98
|
+
if (value == null)
|
|
99
|
+
return "";
|
|
100
|
+
const str = String(value);
|
|
101
|
+
// Escape quotes
|
|
102
|
+
if (str.includes('"') || str.includes(',') || str.includes('\n')) {
|
|
103
|
+
return `"${str.replace(/"/g, '""')}"`;
|
|
104
|
+
}
|
|
105
|
+
return str;
|
|
106
|
+
}
|
|
107
|
+
export function xmlNodeToObjects(xmlString, parentNodeName) {
|
|
108
|
+
const parser = new DOMParser();
|
|
109
|
+
const xml = parser.parseFromString(xmlString, "application/xml");
|
|
110
|
+
const nodes = Array.from(xml.getElementsByTagName(parentNodeName));
|
|
111
|
+
if (nodes.length === 0) {
|
|
112
|
+
throw new Error(`No <${parentNodeName}> nodes found`);
|
|
113
|
+
}
|
|
114
|
+
return nodes.map(node => {
|
|
115
|
+
var _a, _b;
|
|
116
|
+
const obj = {};
|
|
117
|
+
const fields = Array.from(node.getElementsByTagName("Field"));
|
|
118
|
+
for (const field of fields) {
|
|
119
|
+
const key = field.getAttribute("name");
|
|
120
|
+
if (!key)
|
|
121
|
+
continue;
|
|
122
|
+
obj[key] = (_b = (_a = field.textContent) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : "";
|
|
123
|
+
}
|
|
124
|
+
return obj;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Download an array of objects as a CSV file
|
|
129
|
+
* @param csvData Array of objects representing CSV rows
|
|
130
|
+
*/
|
|
131
|
+
export const downloadCSV = (csvData) => {
|
|
132
|
+
if (!csvData.length) {
|
|
133
|
+
showStatus('No data to download', 'error');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const csvContent = stringCsv(csvData);
|
|
137
|
+
// Create Blob and download link
|
|
138
|
+
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
|
139
|
+
const link = document.createElement('a');
|
|
140
|
+
link.href = URL.createObjectURL(blob);
|
|
141
|
+
link.download = `data_${Date.now()}.csv`;
|
|
142
|
+
link.click();
|
|
143
|
+
// Clean up object URL after download
|
|
144
|
+
setTimeout(() => URL.revokeObjectURL(link.href), 100);
|
|
145
|
+
};
|
|
146
|
+
function showStatus(message, type) {
|
|
147
|
+
console.log(`[${type.toUpperCase()}] ${message}`);
|
|
148
|
+
}
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -29,6 +29,7 @@ A lightweight utility library for interacting with **Oracle Field Service Cloud
|
|
|
29
29
|
- getActivitybyId( clientId: string, clientSecret: string,instanceUrl: string, activityId: number, token: string=")
|
|
30
30
|
- updateResource(clientId: string, clientSecret: string, instanceUrl: string, resourceId: string, payload: any, token: string = "")
|
|
31
31
|
- AllResources(clientId: string,clientSecret: string,instanceUrl: string,initialToken = "")
|
|
32
|
+
- AllUsers(clientId: string,clientSecret: string,instanceUrl: string,initialToken = "")
|
|
32
33
|
|
|
33
34
|
### Post Clone
|
|
34
35
|
- resetResourcesEmail(clientId: string,clientSecret: string,instanceUrl: string,newdomain: string = "noreply",token: string = "")
|
|
@@ -94,6 +95,18 @@ A lightweight utility library for interacting with **Oracle Field Service Cloud
|
|
|
94
95
|
</script>
|
|
95
96
|
```
|
|
96
97
|
|
|
98
|
+
```js
|
|
99
|
+
// Download all Resources
|
|
100
|
+
const res = await window.OFSC.Resources.AllResources(
|
|
101
|
+
clientID,
|
|
102
|
+
clientSecret,
|
|
103
|
+
instanceId,
|
|
104
|
+
""
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
window.OFSC.Utilities.downloadCSV(res);
|
|
108
|
+
```
|
|
109
|
+
|
|
97
110
|
## License
|
|
98
111
|
|
|
99
112
|
MIT
|