primate 0.5.2 → 0.6.2
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 +21 -36
- package/debris.json +1 -2
- package/package.json +7 -10
- package/source/App.js +36 -0
- package/source/Bundler.js +51 -0
- package/source/{server/Directory.js → Directory.js} +0 -0
- package/source/{server/EagerPromise.js → EagerPromise.js} +5 -7
- package/source/{server/File.js → File.js} +0 -0
- package/source/Router.js +28 -0
- package/source/Server.js +105 -0
- package/source/{server/Session.js → Session.js} +2 -21
- package/source/{server/attributes.js → attributes.js} +0 -0
- package/source/{server/cache.js → cache.js} +0 -0
- package/source/{server/conf.js → conf.js} +1 -1
- package/source/{server/crypto.js → crypto.js} +0 -0
- package/source/{server/domain → domain}/Domain.js +7 -9
- package/source/{server/domain → domain}/Field.js +0 -0
- package/source/{server/domain → domain}/Predicate.js +0 -0
- package/source/{server/errors → errors}/InternalServer.js +0 -0
- package/source/{server/errors → errors}/Predicate.js +0 -0
- package/source/{server/errors.js → errors.js} +0 -0
- package/source/{server/exports.js → exports.js} +6 -3
- package/source/{server/extend_object.js → extend_object.js} +0 -0
- package/source/handlers/DOM/Node.js +184 -0
- package/source/{server/view → handlers/DOM}/Parser.js +22 -18
- package/source/handlers/html.js +30 -0
- package/source/handlers/http.js +6 -0
- package/source/handlers/json.js +6 -0
- package/source/handlers/redirect.js +10 -0
- package/source/{server/servers/http-codes.json → http-codes.json} +0 -0
- package/source/{server/invariants.js → invariants.js} +0 -0
- package/source/{server/log.js → log.js} +0 -0
- package/source/{server/servers/mimes.json → mimes.json} +0 -0
- package/source/preset/primate.json +3 -7
- package/source/preset/static/index.html +0 -2
- package/source/preset/stores/default.js +1 -1
- package/source/{server/sanitize.js → sanitize.js} +0 -0
- package/source/{server/store → store}/Memory.js +0 -0
- package/source/{server/store → store}/Store.js +1 -1
- package/source/{server/types → types}/Array.js +0 -0
- package/source/{server/types → types}/Boolean.js +0 -0
- package/source/{server/types → types}/Date.js +0 -0
- package/source/{server/types → types}/Domain.js +0 -0
- package/source/{server/types → types}/Instance.js +0 -0
- package/source/{server/types → types}/Number.js +0 -0
- package/source/{server/types → types}/Object.js +0 -0
- package/source/{server/types → types}/Primitive.js +0 -0
- package/source/{server/types → types}/Storeable.js +0 -0
- package/source/{server/types → types}/String.js +0 -0
- package/source/{server/types → types}/errors/Array.json +0 -0
- package/source/{server/types → types}/errors/Boolean.json +0 -0
- package/source/{server/types → types}/errors/Date.json +0 -0
- package/source/{server/types → types}/errors/Number.json +0 -0
- package/source/{server/types → types}/errors/Object.json +0 -0
- package/source/{server/types → types}/errors/String.json +0 -0
- package/source/{server/types.js → types.js} +0 -0
- package/source/client/Action.js +0 -157
- package/source/client/App.js +0 -16
- package/source/client/Client.js +0 -61
- package/source/client/Context.js +0 -47
- package/source/client/Element.js +0 -249
- package/source/client/Node.js +0 -13
- package/source/client/Session.js +0 -27
- package/source/client/View.js +0 -89
- package/source/client/document.js +0 -6
- package/source/client/exports.js +0 -15
- package/source/preset/client/Element.js +0 -2
- package/source/preset/client/app.js +0 -2
- package/source/server/Action.js +0 -100
- package/source/server/App.js +0 -62
- package/source/server/Bundler.js +0 -177
- package/source/server/Context.js +0 -97
- package/source/server/Projector.js +0 -86
- package/source/server/Router.js +0 -52
- package/source/server/domain/domains.js +0 -31
- package/source/server/servers/Dynamic.js +0 -57
- package/source/server/servers/Server.js +0 -5
- package/source/server/servers/Static.js +0 -117
- package/source/server/view/TreeNode.js +0 -197
- package/source/server/view/View.js +0 -35
package/source/server/Bundler.js
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import {dirname} from "path";
|
|
2
|
-
import {fileURLToPath} from "url";
|
|
3
|
-
import Directory from "./Directory.js";
|
|
4
|
-
import File from "./File.js";
|
|
5
|
-
import {algorithm, hash} from "./crypto.js";
|
|
6
|
-
|
|
7
|
-
const meta_url = fileURLToPath(import.meta.url);
|
|
8
|
-
const directory = dirname(meta_url);
|
|
9
|
-
const preset = `${directory}/../preset`;
|
|
10
|
-
|
|
11
|
-
const ending = 3;
|
|
12
|
-
|
|
13
|
-
const s_declarations = () =>
|
|
14
|
-
"// Declarations\n"
|
|
15
|
-
+ "const contexts = {};\n"
|
|
16
|
-
+ "const routes = {};\n\n";
|
|
17
|
-
|
|
18
|
-
const stringify_action = (context, domain, action, i, j, k) => {
|
|
19
|
-
const import_name = `route{i}_${j}_${k}`;
|
|
20
|
-
return `import ${import_name} from "./${context}/${domain}/${action}.js";\n`
|
|
21
|
-
+ `routes["${context}"]["${domain}"]["${action}"] = ${import_name};\n`;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const stringify_actions = ({context, domain, actions = []}, i, j) =>
|
|
25
|
-
actions.reduce((s, action, k) =>
|
|
26
|
-
s + stringify_action(context, domain, action, i, j, k), "");
|
|
27
|
-
|
|
28
|
-
const stringify_domain = (context, domain, i, j) =>
|
|
29
|
-
`routes["${context}"]["${domain.domain}"] = {};\n`
|
|
30
|
-
+ stringify_actions(domain, i, j);
|
|
31
|
-
|
|
32
|
-
const stringify_domains = ({context, domains}, i) =>
|
|
33
|
-
domains.reduce((s, domain, j) =>
|
|
34
|
-
s + stringify_domain(context, domain, i, j), "");
|
|
35
|
-
|
|
36
|
-
const stringify_context = (context, i) =>
|
|
37
|
-
`import context${i} from "./${context.context}/context.js";\n`
|
|
38
|
-
+ `contexts["${context.context}"] = context${i};\n`
|
|
39
|
-
+ `routes["${context.context}"] = {};\n`
|
|
40
|
-
+ stringify_domains(context, i) + "\n";
|
|
41
|
-
|
|
42
|
-
const s_exports = () =>
|
|
43
|
-
"// Exports\n"
|
|
44
|
-
+ "export {contexts, routes};\n";
|
|
45
|
-
|
|
46
|
-
const stringify = actions =>
|
|
47
|
-
actions.reduce((s, context, i) => s + stringify_context(context, i), "");
|
|
48
|
-
|
|
49
|
-
export default class Bundler {
|
|
50
|
-
constructor(conf) {
|
|
51
|
-
this.conf = conf;
|
|
52
|
-
this.debug = conf.debug;
|
|
53
|
-
this.index = conf.files.index;
|
|
54
|
-
this.scripts = [];
|
|
55
|
-
this.hashes = new Set();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async copy_with_preset(subdirectory, to) {
|
|
59
|
-
const {paths} = this.conf;
|
|
60
|
-
|
|
61
|
-
// copy files preset files first
|
|
62
|
-
await File.copy(`${preset}/${subdirectory}`, to);
|
|
63
|
-
|
|
64
|
-
// copy any user code over it, not recreating the folder
|
|
65
|
-
try {
|
|
66
|
-
await File.copy(paths[subdirectory], to, false);
|
|
67
|
-
} catch(error) {
|
|
68
|
-
// directory doesn't exist
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async bundle() {
|
|
73
|
-
const {paths} = this.conf;
|
|
74
|
-
const client_directory = `${paths.public}/client`;
|
|
75
|
-
|
|
76
|
-
// copy static files to public
|
|
77
|
-
await this.copy_with_preset("static", paths.public);
|
|
78
|
-
|
|
79
|
-
// copy client files to public/client
|
|
80
|
-
await this.copy_with_preset("client", client_directory);
|
|
81
|
-
|
|
82
|
-
const primate_js = new File(`${client_directory}/primate.js`);
|
|
83
|
-
await primate_js.write("export * from \"./primate/exports.js\";");
|
|
84
|
-
|
|
85
|
-
// write dynamic exports to public/client/exports.js
|
|
86
|
-
await this.write_exports();
|
|
87
|
-
|
|
88
|
-
// copy framework code to public/client/primate
|
|
89
|
-
await File.copy(`${directory}/../client`, `${client_directory}/primate`);
|
|
90
|
-
|
|
91
|
-
await this.register_scripts();
|
|
92
|
-
|
|
93
|
-
// read index.html from public, then remove it (we serve it dynamically)
|
|
94
|
-
const index_html = await File.read(`${paths.public}/${this.index}`);
|
|
95
|
-
await File.remove(`${paths.public}/${this.index}`);
|
|
96
|
-
|
|
97
|
-
const body = `return \`${index_html}\``;
|
|
98
|
-
const index = new Function("conf", "client", body)(this.conf, this.client);
|
|
99
|
-
|
|
100
|
-
return {index, "hashes": this.hashes};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async register_scripts() {
|
|
104
|
-
const scripts = await this.collect("client");
|
|
105
|
-
Promise.all(scripts.map(async script =>
|
|
106
|
-
this.register(script, await File.read(this.conf.paths.public, script))
|
|
107
|
-
));
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async collect(path) {
|
|
111
|
-
// scan public/client and collect scripts to be added as tags
|
|
112
|
-
let scripts = [];
|
|
113
|
-
const files = await Directory.list(`${this.conf.paths.public}/${path}`);
|
|
114
|
-
for (const file of files) {
|
|
115
|
-
if (file.endsWith(".js")) {
|
|
116
|
-
scripts.push(`${path}/${file}`);
|
|
117
|
-
} else {
|
|
118
|
-
scripts = scripts.concat(await this.collect(`${path}/${file}`));
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return scripts;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
register(src, source) {
|
|
126
|
-
const integrity = `${algorithm}-${hash(source)}`;
|
|
127
|
-
this.scripts.push({"src": `${this.conf.base}${src}`, integrity});
|
|
128
|
-
this.hashes.add(integrity);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
get public_client() {
|
|
132
|
-
return `${this.conf.paths.public}/client`;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
get client() {
|
|
136
|
-
let client = "";
|
|
137
|
-
for (const {integrity, src} of this.scripts) {
|
|
138
|
-
client += `<script type="module" integrity="${integrity}" src="${src}">`
|
|
139
|
-
+ "</script>\n";
|
|
140
|
-
}
|
|
141
|
-
return `${client}<first-view />`;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
async write_exports() {
|
|
145
|
-
const exports = new File(this.public_client, "exports.js");
|
|
146
|
-
|
|
147
|
-
let export_string = s_declarations();
|
|
148
|
-
export_string += stringify(await this.read());
|
|
149
|
-
export_string += s_exports();
|
|
150
|
-
|
|
151
|
-
await exports.write(export_string);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
async read() {
|
|
155
|
-
const path = this.public_client;
|
|
156
|
-
|
|
157
|
-
const contexts = (await Directory.list(path))
|
|
158
|
-
.filter(file => !file.endsWith(".js"))
|
|
159
|
-
.map(context => ({context}));
|
|
160
|
-
|
|
161
|
-
const context_domains = await Promise.all(contexts.map(async ({context}) =>
|
|
162
|
-
({context, "domains": (await Directory.list(path, context))
|
|
163
|
-
.filter(file => !file.endsWith(".js"))
|
|
164
|
-
.map(domain => ({domain})),
|
|
165
|
-
})));
|
|
166
|
-
|
|
167
|
-
const actions = await Promise.all(context_domains
|
|
168
|
-
.map(({context, domains}) =>
|
|
169
|
-
Promise.all(domains.map(async ({domain}) => ({
|
|
170
|
-
context, domain,
|
|
171
|
-
"actions": (await Directory.list(path, context, domain))
|
|
172
|
-
.map(action => action.slice(0, -ending)),
|
|
173
|
-
})))));
|
|
174
|
-
|
|
175
|
-
return context_domains;
|
|
176
|
-
}
|
|
177
|
-
}
|
package/source/server/Context.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import {resolve} from "path";
|
|
2
|
-
import File from "./File.js";
|
|
3
|
-
import Action from "./Action.js";
|
|
4
|
-
import {InternalServerError} from "./errors.js";
|
|
5
|
-
import {assert} from "./invariants.js";
|
|
6
|
-
import cache from "./cache.js";
|
|
7
|
-
import log from "./log.js";
|
|
8
|
-
|
|
9
|
-
export default class Context {
|
|
10
|
-
static directory = "server";
|
|
11
|
-
|
|
12
|
-
constructor(name) {
|
|
13
|
-
this.name = name;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
static before(action) {
|
|
17
|
-
return action;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
static prepare(action, data) {
|
|
21
|
-
return {action, data};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
static get(name) {
|
|
25
|
-
return cache(this, name, async () => {
|
|
26
|
-
const directory = resolve(`${this.directory}/${name}`);
|
|
27
|
-
assert(await File.exists(directory), () => {
|
|
28
|
-
throw new InternalServerError(`missing context directory \`${name}\``);
|
|
29
|
-
});
|
|
30
|
-
try {
|
|
31
|
-
return new (await import(`${directory}/context.js`)).default(name);
|
|
32
|
-
} catch (error) {
|
|
33
|
-
return new this(name);
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
get Class() {
|
|
39
|
-
return this.constructor;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
get directory() {
|
|
43
|
-
return `${this.Class.directory}/${this.name}`;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
get layouts() {
|
|
47
|
-
return cache(this, "layouts", () => {
|
|
48
|
-
const ending = -5;
|
|
49
|
-
const path = `${this.directory}/layouts`;
|
|
50
|
-
|
|
51
|
-
return new File(path).list().reduce(async (sofar, file) => {
|
|
52
|
-
const layouts = await sofar;
|
|
53
|
-
layouts[file.slice(0, ending)] = await File.read(`${path}/${file}`);
|
|
54
|
-
return layouts;
|
|
55
|
-
}, {});
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
log(color, message) {
|
|
60
|
-
log[color](this.name).reset(message).nl();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async handle(error, action) {
|
|
64
|
-
if (this.Class.error !== undefined) {
|
|
65
|
-
this.Class.error(action);
|
|
66
|
-
const response = await action.response();
|
|
67
|
-
assert(action.request.pathname !== response.location, () => {
|
|
68
|
-
throw new InternalServerError("redirect loop detected");
|
|
69
|
-
});
|
|
70
|
-
this.log("red", "handling error with context error handler");
|
|
71
|
-
return response;
|
|
72
|
-
} else {
|
|
73
|
-
throw new InternalServerError(error.message);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
catch(error, action) {
|
|
78
|
-
if (error instanceof InternalServerError) {
|
|
79
|
-
// cannot proceed, throw up
|
|
80
|
-
throw error;
|
|
81
|
-
} else {
|
|
82
|
-
return this.handle(error, action);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async run(request, session) {
|
|
87
|
-
const {type = "read"} = request;
|
|
88
|
-
try {
|
|
89
|
-
const action = await Action.new(request, session, this);
|
|
90
|
-
await this.Class.before(action);
|
|
91
|
-
await action.run(type);
|
|
92
|
-
return action.response();
|
|
93
|
-
} catch(error) {
|
|
94
|
-
return this.catch(error, new Action(request, session, this));
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import Domain from "./domain/Domain.js";
|
|
2
|
-
import {actuals} from "./domain/domains.js";
|
|
3
|
-
import log from "./log.js";
|
|
4
|
-
|
|
5
|
-
const scalar = async (document, property) => {
|
|
6
|
-
const value = await document[property];
|
|
7
|
-
return value instanceof Domain ? {} : value;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export default class Projector {
|
|
11
|
-
constructor(documents, projection = []) {
|
|
12
|
-
this.cache = {};
|
|
13
|
-
for (const actual in actuals) {
|
|
14
|
-
this.cache[actual] = {};
|
|
15
|
-
}
|
|
16
|
-
this.documents = documents;
|
|
17
|
-
this.projection = projection.reduce((sofar, domain) => {
|
|
18
|
-
const [key] = Object.keys(domain);
|
|
19
|
-
sofar[key] = domain[key];
|
|
20
|
-
return sofar;
|
|
21
|
-
}, {});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
many(documents, projection) {
|
|
25
|
-
return Promise.all(documents.map(d => this.resolve(d, projection)));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async by_store(key, document, projection) {
|
|
29
|
-
return this.resolve(await document[key], projection[key]);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async by_cache(space, key, document, projection) {
|
|
33
|
-
const cache = this.cache[space];
|
|
34
|
-
const key_id = document[`${key}_id`];
|
|
35
|
-
if (cache[key_id] === undefined) {
|
|
36
|
-
cache[key_id] = await this.by_store(key, document, projection);
|
|
37
|
-
}
|
|
38
|
-
return cache[key_id];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
object(document, projection, key, fields) {
|
|
42
|
-
return fields?.[key] === undefined
|
|
43
|
-
? this.by_store(key, document, projection)
|
|
44
|
-
: this.by_cache(fields[key], key, document, projection);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async one(document, projection) {
|
|
48
|
-
if (document === undefined) {
|
|
49
|
-
return undefined;
|
|
50
|
-
}
|
|
51
|
-
const resolved = {};
|
|
52
|
-
let fields = undefined;
|
|
53
|
-
if (document instanceof Domain && document.Class.name !== "default") {
|
|
54
|
-
fields = actuals[document.Class.name];
|
|
55
|
-
}
|
|
56
|
-
for (const property of projection) {
|
|
57
|
-
if (typeof property === "string") {
|
|
58
|
-
resolved[property] = await scalar(document, property);
|
|
59
|
-
} else if (typeof property === "object") {
|
|
60
|
-
const [key] = Object.keys(property);
|
|
61
|
-
resolved[key] = await this.object(document, property, key, fields);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return resolved;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
resolve(document, projection) {
|
|
68
|
-
return this[Array.isArray(document) ? "many" : "one"](document, projection);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async project() {
|
|
72
|
-
return (await Promise.all(Object.keys(this.documents).map(async key => {
|
|
73
|
-
const resolved = this.projection[key] === undefined
|
|
74
|
-
? undefined
|
|
75
|
-
: await this.resolve(await this.documents[key], this.projection[key]);
|
|
76
|
-
return {key, resolved};
|
|
77
|
-
}))).reduce((projected, {key, resolved}) => {
|
|
78
|
-
if (resolved === undefined) {
|
|
79
|
-
log.yellow(` \`${key}\` not projected`).nl();
|
|
80
|
-
} else {
|
|
81
|
-
projected[key] = resolved;
|
|
82
|
-
}
|
|
83
|
-
return projected;
|
|
84
|
-
}, {});
|
|
85
|
-
}
|
|
86
|
-
}
|
package/source/server/Router.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
export default class Router {
|
|
2
|
-
constructor(routes = [], conf) {
|
|
3
|
-
this.conf = conf;
|
|
4
|
-
this.routes = {};
|
|
5
|
-
routes.forEach(({from, to, contexts}) => contexts.forEach(context => {
|
|
6
|
-
this.routes[context] = this.routes[context] ?? [];
|
|
7
|
-
this.routes[context].push({"from": new RegExp(`^${from}$`, "u"), to});
|
|
8
|
-
}));
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
context(context) {
|
|
12
|
-
return context ?? this.conf.defaults.context;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
debase(pathname) {
|
|
16
|
-
return pathname.replace(this.conf.base, "/");
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
route_by_context(context, path) {
|
|
20
|
-
return this.routes[context]?.find(({from}) => from.test(path)) ?? [];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
resolve_by_context(pathname, context) {
|
|
24
|
-
const [path, search = ""] = pathname.split("?");
|
|
25
|
-
|
|
26
|
-
const route = this.route_by_context(context, path);
|
|
27
|
-
if (route === undefined) {
|
|
28
|
-
return pathname;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const replace = path.replace(route.from, route.to);
|
|
32
|
-
return `${replace}${replace.includes("?") ? "" : "?"}&${search}`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
resolve(pathname, context) {
|
|
36
|
-
return this.context(context) === undefined
|
|
37
|
-
? pathname
|
|
38
|
-
: this.resolve_by_context(pathname, context);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async route(pathname, context) {
|
|
42
|
-
const resolved = await this.resolve(this.debase(pathname), context);
|
|
43
|
-
const url = new URL(`https://primatejs.com/${resolved}`);
|
|
44
|
-
const parts = url.pathname.split("/").filter(part => part !== "");
|
|
45
|
-
const [namespace, action, _id] = parts;
|
|
46
|
-
return {
|
|
47
|
-
"pathname": url.pathname, resolved, parts,
|
|
48
|
-
"path": {namespace, action, _id},
|
|
49
|
-
"params": {...Object.fromEntries(url.searchParams)},
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import conf from "../conf.js";
|
|
2
|
-
import File from "../File.js";
|
|
3
|
-
import Field from "./Field.js";
|
|
4
|
-
import Domain from "./Domain.js";
|
|
5
|
-
|
|
6
|
-
const domains = {};
|
|
7
|
-
const base = conf().paths.domains;
|
|
8
|
-
|
|
9
|
-
for (const domain of await new File(base).list(".js")) {
|
|
10
|
-
const name = domain.slice(0, -3);
|
|
11
|
-
import(`${base}/${domain}`).then(module => {
|
|
12
|
-
domains[name] = module.default;
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const actuals = {};
|
|
17
|
-
for (const domain in domains) {
|
|
18
|
-
if (domains[domain].prototype instanceof Domain) {
|
|
19
|
-
const fields = {};
|
|
20
|
-
for (const field in domains[domain]._fields) {
|
|
21
|
-
const Type = domains[domain]._fields[field].Type;
|
|
22
|
-
if(Type.prototype instanceof Domain) {
|
|
23
|
-
fields[field.slice(0, -3)] = Type.name;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
actuals[domain] = fields;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
export default domains;
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import {WebSocketServer} from "ws";
|
|
2
|
-
import Server from "./Server.js";
|
|
3
|
-
import Session from "../Session.js";
|
|
4
|
-
|
|
5
|
-
const options = {"perMessageDeflate": false};
|
|
6
|
-
|
|
7
|
-
export default class DynamicServer extends Server {
|
|
8
|
-
run() {
|
|
9
|
-
const {server, path} = this.conf;
|
|
10
|
-
return new WebSocketServer({...options, server, path})
|
|
11
|
-
.on("connection", this.connected.bind(this));
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async connected(socket, request) {
|
|
15
|
-
const {context} = this.conf;
|
|
16
|
-
socket.session = await Session.get(request.headers.cookie, context);
|
|
17
|
-
socket.on("message", event => this.try(socket, event));
|
|
18
|
-
// inform client that we're connected and it can start sending messages
|
|
19
|
-
socket.send("open");
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async try(socket, event) {
|
|
23
|
-
const {session} = socket;
|
|
24
|
-
const request = JSON.parse(event);
|
|
25
|
-
try {
|
|
26
|
-
await this.serve(request, socket);
|
|
27
|
-
await session.log("green", `${request.type} ${request.url}`);
|
|
28
|
-
} catch(error) {
|
|
29
|
-
await session.log("red", error.message);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async serve(request, socket) {
|
|
34
|
-
const {router} = this.conf;
|
|
35
|
-
const {session} = socket;
|
|
36
|
-
const {path, params} = await session.route(router, request.url);
|
|
37
|
-
const response = await session.run({...request, path, params});
|
|
38
|
-
const {type} = response;
|
|
39
|
-
return this[type]?.(socket, response)
|
|
40
|
-
?? DynamicServer.serve(socket, response);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
static serve(socket, response) {
|
|
44
|
-
socket.send(JSON.stringify(response));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
redirect(socket, {location}) {
|
|
48
|
-
if (location.startsWith("https://")) {
|
|
49
|
-
// redirect externally
|
|
50
|
-
return undefined;
|
|
51
|
-
} else {
|
|
52
|
-
const url = location;
|
|
53
|
-
this.try(socket, JSON.stringify({"pathname": url, url, "type": "read"}));
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import zlib from "zlib";
|
|
2
|
-
import {Readable} from "stream";
|
|
3
|
-
import {createServer} from "https";
|
|
4
|
-
import {join} from "path";
|
|
5
|
-
import {parse} from "url";
|
|
6
|
-
import Server from "./Server.js";
|
|
7
|
-
import Session from "../Session.js";
|
|
8
|
-
import File from "../File.js";
|
|
9
|
-
import {algorithm, hash} from "../crypto.js";
|
|
10
|
-
import log from "../log.js";
|
|
11
|
-
import codes from "./http-codes.json" assert {"type": "json"};
|
|
12
|
-
import mimes from "./mimes.json" assert {"type": "json"};
|
|
13
|
-
|
|
14
|
-
const regex = /\.([a-z1-9]*)$/u;
|
|
15
|
-
const mime = filename => mimes[filename.match(regex)[1]] ?? mimes.binary;
|
|
16
|
-
|
|
17
|
-
const stream = (from, response) => {
|
|
18
|
-
response.setHeader("Content-Encoding", "br");
|
|
19
|
-
response.writeHead(codes.OK);
|
|
20
|
-
return from.pipe(zlib.createBrotliCompress())
|
|
21
|
-
.pipe(response)
|
|
22
|
-
.on("close", () => response.end());
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export default class StaticServer extends Server {
|
|
26
|
-
async run() {
|
|
27
|
-
const {http, context} = this.conf;
|
|
28
|
-
const {csp, "same-site": same_site = "Strict"} = http;
|
|
29
|
-
this.csp = Object.keys(csp).reduce((policy_string, key) =>
|
|
30
|
-
policy_string + `${key} ${csp[key]};`, "");
|
|
31
|
-
|
|
32
|
-
this.server = await createServer(http, async (request, response) => {
|
|
33
|
-
const session = await Session.get(request.headers.cookie, context);
|
|
34
|
-
if (!session.has_cookie) {
|
|
35
|
-
const {cookie} = session;
|
|
36
|
-
response.setHeader("Set-Cookie", `${cookie}; SameSite=${same_site}`);
|
|
37
|
-
}
|
|
38
|
-
response.session = session;
|
|
39
|
-
request.on("end", () =>
|
|
40
|
-
this.try(parse(request.url).path, request, response)
|
|
41
|
-
).resume();
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async try(url, request, response) {
|
|
46
|
-
try {
|
|
47
|
-
await this.serve(url, request, response);
|
|
48
|
-
} catch (error) {
|
|
49
|
-
await response.session.log("red", error.message);
|
|
50
|
-
response.writeHead(codes.InternalServerError);
|
|
51
|
-
response.end();
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async serve_file(url, filename, file, response) {
|
|
56
|
-
response.setHeader("Content-Type", mime(filename));
|
|
57
|
-
response.setHeader("Etag", file.modified);
|
|
58
|
-
await response.session.log("green", url);
|
|
59
|
-
return stream(file.read_stream, response);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async serve(url, request, response) {
|
|
63
|
-
const filename = join(this.conf.serve_from, url);
|
|
64
|
-
const file = await new File(filename);
|
|
65
|
-
return await file.is_file
|
|
66
|
-
? this.serve_file(url, filename, file, response)
|
|
67
|
-
: this.serve_data(url, request, response);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async serve_data(pathname, request, response) {
|
|
71
|
-
const {session} = response;
|
|
72
|
-
const {path, params} = await session.route(this.conf.router, pathname);
|
|
73
|
-
await session.log("green", `read ${pathname}`);
|
|
74
|
-
const data = await session.run({path, params, pathname, "url": pathname});
|
|
75
|
-
const handler = StaticServer[data.type] ?? this.index.bind(this);
|
|
76
|
-
return handler(data, response);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
index(data, response) {
|
|
80
|
-
const {hashes, index} = this.conf;
|
|
81
|
-
const src = "import {app} from './client/primate.js';"
|
|
82
|
-
+ `app.client.session.run(${JSON.stringify(data)});`;
|
|
83
|
-
const integrity = `${algorithm}-${hash(src)}`;
|
|
84
|
-
const view = `<script type="module" integrity="${integrity}">${src}`
|
|
85
|
-
+ "</script>";
|
|
86
|
-
const file = index.replace("<first-view />", () => view);
|
|
87
|
-
const script_src = Array.from(hashes)
|
|
88
|
-
.concat([integrity])
|
|
89
|
-
.reduce((hash_string, next_hash) => hash_string + ` '${next_hash}'`, "");
|
|
90
|
-
|
|
91
|
-
response.setHeader("Content-Security-Policy",
|
|
92
|
-
this.csp + `script-src 'self'${script_src};`);
|
|
93
|
-
response.setHeader("Content-Type", "text/html");
|
|
94
|
-
response.setHeader("Referrer-Policy", "same-origin");
|
|
95
|
-
return stream(Readable.from([file]), response);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
listen(port, host) {
|
|
99
|
-
log.reset("on").yellow(`https://${host}:${port}`).nl();
|
|
100
|
-
this.server.listen(port, host);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
static redirect({location}, response) {
|
|
104
|
-
response.setHeader("Location", location);
|
|
105
|
-
response.writeHead(codes.Found);
|
|
106
|
-
return response.end();
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
static return({payload}, response) {
|
|
110
|
-
if (payload instanceof File) {
|
|
111
|
-
return stream(payload.stream, response);
|
|
112
|
-
} else {
|
|
113
|
-
response.setHeader("Content-Type", "application/json");
|
|
114
|
-
return stream(Readable.from([JSON.stringify(payload)]), response);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|