@cocreate/lazy-loader 1.9.0 → 1.11.0
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/CHANGELOG.md +20 -0
- package/package.json +2 -1
- package/src/client.js +61 -0
- package/src/index.js +13 -60
- package/src/server.js +278 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
# [1.11.0](https://github.com/CoCreate-app/CoCreate-lazy-loader/compare/v1.10.0...v1.11.0) (2023-12-09)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* add "@cocreate/config" ([bac22b5](https://github.com/CoCreate-app/CoCreate-lazy-loader/commit/bac22b5d4e5bafd83e68fe1ca727b184f07e8231))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* handle http and websocket requests ([07b205c](https://github.com/CoCreate-app/CoCreate-lazy-loader/commit/07b205c31527e10c55f716a6ea1db243bebcccfd))
|
|
12
|
+
|
|
13
|
+
# [1.10.0](https://github.com/CoCreate-app/CoCreate-lazy-loader/compare/v1.9.0...v1.10.0) (2023-12-01)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* get config using CoCreate-config ([8e0b9fc](https://github.com/CoCreate-app/CoCreate-lazy-loader/commit/8e0b9fce220c493a1db250cf424345d7708a2e08))
|
|
19
|
+
* lazyload client and server modules ([80e25d6](https://github.com/CoCreate-app/CoCreate-lazy-loader/commit/80e25d67d729eaec18775a43bf1ef10cb9141aa8))
|
|
20
|
+
|
|
1
21
|
# [1.9.0](https://github.com/CoCreate-app/CoCreate-lazy-loader/compare/v1.8.0...v1.9.0) (2023-11-25)
|
|
2
22
|
|
|
3
23
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cocreate/lazy-loader",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "A simple lazy-loader component in vanilla javascript. Easily configured using HTML5 data-attributes and/or JavaScript API.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lazy-loader",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"webpack-log": "^3.0.1"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
+
"@cocreate/config": "^1.10.0",
|
|
61
62
|
"@cocreate/observer": "^1.14.0"
|
|
62
63
|
}
|
|
63
64
|
}
|
package/src/client.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import observer from '@cocreate/observer';
|
|
2
|
+
|
|
3
|
+
function listen(callback, selector) {
|
|
4
|
+
|
|
5
|
+
function observerCallback({ target }) {
|
|
6
|
+
// let isInit = target.querySelector(selector)/testtt
|
|
7
|
+
// if (isInit) {
|
|
8
|
+
callback()
|
|
9
|
+
// console.log('lazyloaded', selector)
|
|
10
|
+
observer.uninit(observerCallback)
|
|
11
|
+
// }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
observer.init({
|
|
15
|
+
name: 'lazyloadObserver',
|
|
16
|
+
observe: ['childList'],
|
|
17
|
+
target: selector,
|
|
18
|
+
callback: observerCallback
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
let selectorAttributes = [];
|
|
22
|
+
let attributes = selector.split(",")
|
|
23
|
+
for (let attribute of attributes) {
|
|
24
|
+
let attr = attribute.trim()
|
|
25
|
+
if (attr.startsWith("[")) {
|
|
26
|
+
let pos = attr.indexOf("*")
|
|
27
|
+
if (pos == -1)
|
|
28
|
+
pos = attr.indexOf("=")
|
|
29
|
+
if (pos !== -1) {
|
|
30
|
+
attr = attr.slice(1, pos)
|
|
31
|
+
} else {
|
|
32
|
+
attr = attr.slice(1, -1)
|
|
33
|
+
}
|
|
34
|
+
selectorAttributes.push(attr)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
if (selectorAttributes.length > 0)
|
|
39
|
+
observer.init({
|
|
40
|
+
name: 'lazyloadAttributeObserver',
|
|
41
|
+
observe: ['attributes'],
|
|
42
|
+
attributeName: selectorAttributes,
|
|
43
|
+
target: selector,
|
|
44
|
+
callback: observerCallback
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function lazyLoad(name, selector, callback) {
|
|
50
|
+
if (document.querySelector(selector))
|
|
51
|
+
await dependency(name, await callback())
|
|
52
|
+
else
|
|
53
|
+
listen(callback, selector)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function dependency(name, promise) {
|
|
57
|
+
let component = await promise;
|
|
58
|
+
Object.assign(window.CoCreate, {
|
|
59
|
+
[name]: component.default
|
|
60
|
+
});
|
|
61
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,61 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
function
|
|
4
|
-
|
|
5
|
-
function observerCallback({ target }) {
|
|
6
|
-
// let isInit = target.querySelector(selector)
|
|
7
|
-
// if (isInit) {
|
|
8
|
-
callback()
|
|
9
|
-
// console.log('lazyloaded', selector)
|
|
10
|
-
observer.uninit(observerCallback)
|
|
11
|
-
// }
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
observer.init({
|
|
15
|
-
name: 'lazyloadObserver',
|
|
16
|
-
observe: ['childList'],
|
|
17
|
-
target: selector,
|
|
18
|
-
callback: observerCallback
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
let selectorAttributes = [];
|
|
22
|
-
let attributes = selector.split(",")
|
|
23
|
-
for (let attribute of attributes) {
|
|
24
|
-
let attr = attribute.trim()
|
|
25
|
-
if (attr.startsWith("[")) {
|
|
26
|
-
let pos = attr.indexOf("*")
|
|
27
|
-
if (pos == -1)
|
|
28
|
-
pos = attr.indexOf("=")
|
|
29
|
-
if (pos !== -1) {
|
|
30
|
-
attr = attr.slice(1, pos)
|
|
31
|
-
} else {
|
|
32
|
-
attr = attr.slice(1, -1)
|
|
33
|
-
}
|
|
34
|
-
selectorAttributes.push(attr)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
if (selectorAttributes.length > 0)
|
|
39
|
-
observer.init({
|
|
40
|
-
name: 'lazyloadAttributeObserver',
|
|
41
|
-
observe: ['attributes'],
|
|
42
|
-
attributeName: selectorAttributes,
|
|
43
|
-
target: selector,
|
|
44
|
-
callback: observerCallback
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
if (typeof define === 'function' && define.amd) {
|
|
3
|
+
define(["./client"], function (CoCreateLazyLoader) {
|
|
4
|
+
return factory(CoCreateLazyLoader)
|
|
45
5
|
});
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export async function dependency(name, promise) {
|
|
57
|
-
let component = await promise;
|
|
58
|
-
Object.assign(window.CoCreate, {
|
|
59
|
-
[name]: component.default
|
|
60
|
-
});
|
|
61
|
-
}
|
|
6
|
+
} else if (typeof module === 'object' && module.exports) {
|
|
7
|
+
const CoCreateLazyLoader = require("./server.js")
|
|
8
|
+
module.exports = factory(CoCreateLazyLoader);
|
|
9
|
+
} else {
|
|
10
|
+
root.returnExports = factory(root["./client.js"]);
|
|
11
|
+
}
|
|
12
|
+
}(typeof self !== 'undefined' ? self : this, function (CoCreateLazyLoader) {
|
|
13
|
+
return CoCreateLazyLoader;
|
|
14
|
+
}));
|
package/src/server.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
const fs = require('fs').promises;
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const vm = require('vm');
|
|
4
|
+
const Config = require("@cocreate/config");
|
|
5
|
+
const { URL } = require('url');
|
|
6
|
+
|
|
7
|
+
const organizations = {};
|
|
8
|
+
const hosts = {};
|
|
9
|
+
|
|
10
|
+
class CoCreateLazyLoader {
|
|
11
|
+
constructor(server, crud, files) {
|
|
12
|
+
this.server = server
|
|
13
|
+
this.wsManager = crud.wsManager
|
|
14
|
+
this.crud = crud
|
|
15
|
+
this.files = files
|
|
16
|
+
this.exclusion = { ...require.cache };
|
|
17
|
+
this.modules = {};
|
|
18
|
+
this.init()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async init() {
|
|
22
|
+
// Function to create the scripts directory if it doesn't exist
|
|
23
|
+
async function createScriptsDirectory() {
|
|
24
|
+
try {
|
|
25
|
+
const scriptsDirectory = './scripts';
|
|
26
|
+
await fs.mkdir(scriptsDirectory, { recursive: true });
|
|
27
|
+
console.log(`Scripts directory created at ${scriptsDirectory}`);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error('Error creating scripts directory:', error);
|
|
30
|
+
throw error; // Halt execution if directory creation fails
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Call this function at the start of your application
|
|
35
|
+
createScriptsDirectory();
|
|
36
|
+
|
|
37
|
+
// TODO: return the value so it can be applied directly to modules
|
|
38
|
+
// this.modules[key] = await Config('modules', false, false)
|
|
39
|
+
|
|
40
|
+
const config = await Config('modules', false, false)
|
|
41
|
+
if (!config)
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
for (let name of Object.keys(config.modules)) {
|
|
45
|
+
this.modules[name] = config.modules[name];
|
|
46
|
+
this.wsManager.on(this.modules[name].event, async (data) => {
|
|
47
|
+
this.executeScriptWithTimeout(name, data)
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.server.on('request', async (req, res) => {
|
|
52
|
+
try {
|
|
53
|
+
const valideUrl = new URL(`http://${req.headers.host}${req.url}`);
|
|
54
|
+
const hostname = valideUrl.hostname;
|
|
55
|
+
|
|
56
|
+
let organization = hosts.get(hostname);
|
|
57
|
+
if (!organization) {
|
|
58
|
+
let org = await this.crud.send({
|
|
59
|
+
method: 'object.read',
|
|
60
|
+
array: 'organizations',
|
|
61
|
+
$filter: {
|
|
62
|
+
query: [
|
|
63
|
+
{ key: "host", value: [hostname], operator: "$in" }
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
organization_id: process.env.organization_id
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
if (!org || !org.object || !org.object[0]) {
|
|
70
|
+
if (!hostNotFound)
|
|
71
|
+
hostNotFound = await getDefaultFile('/hostNotFound.html')
|
|
72
|
+
return sendResponse(hostNotFound.object[0].src, 404, { 'Content-Type': 'text/html', 'storage': organization.storage })
|
|
73
|
+
} else {
|
|
74
|
+
organization = org.object[0]
|
|
75
|
+
organizations[organization._id] = organization
|
|
76
|
+
hosts[hostname] = organization
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (valideUrl.pathname.startsWith('/webhooks/')) {
|
|
81
|
+
let name = req.url.split('/')[2]; // Assuming URL structure is /webhook/name/...
|
|
82
|
+
if (this.modules[name]) {
|
|
83
|
+
this.executeScriptWithTimeout(name, { req, res, crud: this.crud, organization, valideUrl })
|
|
84
|
+
} else {
|
|
85
|
+
// Handle unknown module or missing webhook method
|
|
86
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
87
|
+
res.end(JSON.stringify({ error: 'Not found' }));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
} else {
|
|
91
|
+
this.files.send(req, res, this.crud, organization, valideUrl)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
} catch (error) {
|
|
95
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
96
|
+
res.end('Invalid host format');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async executeScriptWithTimeout(name, data) {
|
|
105
|
+
try {
|
|
106
|
+
if (!this.modules[name].content) {
|
|
107
|
+
if (this.modules[name].path)
|
|
108
|
+
this.modules[name].content = await require(this.modules[name].path)
|
|
109
|
+
else {
|
|
110
|
+
try {
|
|
111
|
+
const scriptPath = path.join(scriptsDirectory, `${name}.js`);
|
|
112
|
+
await fs.access(scriptPath);
|
|
113
|
+
this.modules[name].content = await fs.readFile(scriptPath, 'utf8');
|
|
114
|
+
} catch {
|
|
115
|
+
this.modules[name].content = await fetchScriptFromDatabaseAndSave(name, this.modules[name], data);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (this.modules[name].content) {
|
|
121
|
+
data.apis = await this.getApiKey(data.organization_id, name)
|
|
122
|
+
data = await this.modules[name].content.send(data)
|
|
123
|
+
delete data.apis
|
|
124
|
+
if (data.socket)
|
|
125
|
+
this.wsManager.send(data)
|
|
126
|
+
} else
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
if (this.modules[name].unload === false || this.modules[name].unload === 'false')
|
|
130
|
+
return
|
|
131
|
+
else if (this.modules[name].unload === true || this.modules[name].unload === 'true')
|
|
132
|
+
console.log('config should unload after completeion ')
|
|
133
|
+
else if (this.modules[name].unload = parseInt(this.modules[name].unload, 10)) {
|
|
134
|
+
// Check if the script is already loaded
|
|
135
|
+
if (this.modules[name].timeout) {
|
|
136
|
+
clearTimeout(this.modules[name].timeout);
|
|
137
|
+
} else if (!this.modules[name].path) {
|
|
138
|
+
// Execute the script
|
|
139
|
+
this.modules[name].context = new vm.createContext({});
|
|
140
|
+
const script = new vm.Script(this.modules[name].context);
|
|
141
|
+
script.runInContext(context);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Reset or set the timeout
|
|
145
|
+
const timeout = setTimeout(() => {
|
|
146
|
+
// delete this.modules[name]
|
|
147
|
+
delete this.modules[name].timeout
|
|
148
|
+
delete this.modules[name].context
|
|
149
|
+
delete this.modules[name].content
|
|
150
|
+
console.log(`Module ${name} removed due to inactivity.`);
|
|
151
|
+
clearModuleCache(name);
|
|
152
|
+
|
|
153
|
+
}, this.modules[name].unload);
|
|
154
|
+
|
|
155
|
+
this.modules[name].timeout = timeout
|
|
156
|
+
}
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.log(error)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async getApiKey(organization_id, name) {
|
|
163
|
+
organizations[organization_id] = this.getOrganization(organization_id, name)
|
|
164
|
+
organizations[organization_id] = await organizations[organization_id]
|
|
165
|
+
return organizations[organization_id][name]
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async getOrganization(organization_id) {
|
|
169
|
+
let organization = await this.crud.send({
|
|
170
|
+
method: 'object.read',
|
|
171
|
+
database: organization_id,
|
|
172
|
+
array: 'organizations',
|
|
173
|
+
object: [{ _id: organization_id }],
|
|
174
|
+
organization_id
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
if (organization
|
|
178
|
+
&& organization.object
|
|
179
|
+
&& organization.object[0]) {
|
|
180
|
+
if (organization.object[0].apis) {
|
|
181
|
+
return organization.object[0].apis
|
|
182
|
+
} else
|
|
183
|
+
return { error: 'No apis defined could not be found' }
|
|
184
|
+
} else {
|
|
185
|
+
return { serverOrganization: false, error: 'An organization could not be found' }
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function getModuleDependencies(modulePath) {
|
|
194
|
+
let moduleObj = require.cache[modulePath];
|
|
195
|
+
if (!moduleObj) {
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Get all child module paths
|
|
200
|
+
return moduleObj.children.map(child => child.id);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function isModuleUsedElsewhere(modulePath, name) {
|
|
204
|
+
return Object.keys(require.cache).some(path => {
|
|
205
|
+
const moduleObj = require.cache[path];
|
|
206
|
+
// return moduleObj.children.some(child => child.id === modulePath && path !== modulePath);
|
|
207
|
+
return moduleObj.children.some(child => {
|
|
208
|
+
// let test = child.id === modulePath && path !== modulePath
|
|
209
|
+
// if (test)
|
|
210
|
+
// return test
|
|
211
|
+
return child.id === modulePath && path !== modulePath
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function clearModuleCache(name) {
|
|
217
|
+
try {
|
|
218
|
+
const modulePath = require.resolve(name);
|
|
219
|
+
const dependencies = getModuleDependencies(modulePath);
|
|
220
|
+
|
|
221
|
+
// Check if the module is a dependency of other modules
|
|
222
|
+
// const moduleObj = require.cache[modulePath];
|
|
223
|
+
// if (moduleObj && moduleObj.parent) {
|
|
224
|
+
// console.log(`Module ${name} is a dependency of other modules.`);
|
|
225
|
+
// return;
|
|
226
|
+
// }
|
|
227
|
+
|
|
228
|
+
// Check if the module is used by other modules
|
|
229
|
+
if (isModuleUsedElsewhere(modulePath, name)) {
|
|
230
|
+
console.log(`Module ${name} is a dependency of other modules.`);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Remove the module from the cache
|
|
235
|
+
delete require.cache[modulePath];
|
|
236
|
+
console.log(`Module ${name} has been removed from cache.`);
|
|
237
|
+
// Recursively clear dependencies from cache
|
|
238
|
+
dependencies.forEach(depPath => {
|
|
239
|
+
clearModuleCache(depPath);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.error(`Error clearing module cache for ${name}: ${error.message}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Function to fetch script from database and save to disk
|
|
248
|
+
async function fetchScriptFromDatabaseAndSave(name, moduleConfig) {
|
|
249
|
+
let data = {
|
|
250
|
+
method: 'object.read',
|
|
251
|
+
array: moduleConfig.array,
|
|
252
|
+
$filter: {
|
|
253
|
+
query: [
|
|
254
|
+
{ key: "host", value: [moduleConfig.object.hostname, '*'], operator: "$in" },
|
|
255
|
+
{ key: "pathname", value: moduleConfig.object.pathname, operator: "$eq" }
|
|
256
|
+
],
|
|
257
|
+
limit: 1
|
|
258
|
+
},
|
|
259
|
+
organization_id
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
let file = await this.crud.send(data);
|
|
263
|
+
let src;
|
|
264
|
+
|
|
265
|
+
if (file && file.object && file.object[0]) {
|
|
266
|
+
src = file.object[0].src;
|
|
267
|
+
} else {
|
|
268
|
+
throw new Error('Script not found in database');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Save to disk for future use
|
|
272
|
+
const scriptPath = path.join(scriptsDirectory, `${name}.js`);
|
|
273
|
+
await fs.writeFile(scriptPath, src);
|
|
274
|
+
|
|
275
|
+
return src;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
module.exports = CoCreateLazyLoader;
|