@cenk1cenk2/md-printer 2.21.0 → 3.1.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/README.md +333 -48
- package/bin/dev.js +0 -2
- package/bin/run.js +0 -1
- package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs +1 -2
- package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs +1 -2
- package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateParam.mjs +8 -0
- package/dist/commands/index.mjs +32 -345
- package/dist/components/Date.svelte +1 -0
- package/dist/components/Markdown.svelte +20 -0
- package/dist/components/Nodes.svelte +32 -0
- package/dist/components/Page.svelte +20 -0
- package/dist/components/PageBreak.svelte +5 -0
- package/dist/components/PageNumber.svelte +1 -0
- package/dist/components/SignatureBlock.svelte +56 -0
- package/dist/components/Title.svelte +1 -0
- package/dist/components/TotalPages.svelte +1 -0
- package/dist/components/index.ts +8 -0
- package/dist/constants/file.constants.mjs +1 -8
- package/dist/constants/index.mjs +5 -4
- package/dist/constants/preview.constants.mjs +9 -0
- package/dist/constants/signature.constants.mjs +7 -0
- package/dist/constants/template.constants.mjs +48 -10
- package/dist/interfaces/commands/index.mjs +1 -1
- package/dist/interfaces/commands/run.interface.mjs +1 -1
- package/dist/interfaces/index.mjs +1 -1
- package/dist/interfaces/markdown.interface.mjs +1 -0
- package/dist/interfaces/renderer.interface.mjs +1 -0
- package/dist/interfaces/session.interface.mjs +1 -0
- package/dist/interfaces/signature.interface.mjs +1 -1
- package/dist/interfaces/template.interface.mjs +1 -0
- package/dist/modules/md-printer/md-printer.handler.mjs +113 -0
- package/dist/modules/md-printer/md-printer.module.mjs +21 -0
- package/dist/modules/md-printer/md-printer.schema.mjs +50 -0
- package/dist/services/i18n.service.mjs +12 -15
- package/dist/services/index.mjs +9 -2
- package/dist/services/loopback.mjs +18 -0
- package/dist/services/markdown.service.mjs +123 -0
- package/dist/services/printer.module.mjs +39 -0
- package/dist/services/printer.service.mjs +171 -0
- package/dist/services/renderer/browser.mjs +41 -0
- package/dist/services/renderer/browser.service.mjs +56 -0
- package/dist/services/renderer/renderer.service.mjs +198 -0
- package/dist/services/session/Preview.svelte +238 -0
- package/dist/services/session/preview.service.mjs +187 -0
- package/dist/services/session/session.service.mjs +115 -0
- package/dist/services/signature/adapters/a-trust.adapter.mjs +32 -36
- package/dist/services/signature/index.mjs +1 -2
- package/dist/services/signature/signature.adapter.mjs +1 -2
- package/dist/services/signature/signature.service.mjs +6 -10
- package/dist/services/template/Document.svelte +21 -0
- package/dist/services/template/Margin.svelte +15 -0
- package/dist/services/template/assets.mjs +127 -0
- package/dist/services/template/render.entry.ts +32 -0
- package/dist/services/template/svelte.plugin.mjs +66 -0
- package/dist/services/template/template.service.mjs +211 -0
- package/oclif.manifest.json +49 -36
- package/package.json +69 -35
- package/templates/default/Template.svelte +83 -0
- package/templates/default/settings.json +1 -3
- package/templates/invoice/Template.svelte +247 -0
- package/templates/invoice/components/Client.svelte +42 -0
- package/templates/invoice/components/Company.svelte +41 -0
- package/templates/invoice/components/Items.svelte +66 -0
- package/templates/invoice/components/Notes.svelte +21 -0
- package/templates/invoice/components/Payment.svelte +25 -0
- package/templates/invoice/components/Signature.svelte +34 -0
- package/templates/invoice/components/Totals.svelte +47 -0
- package/templates/invoice/round.js +2 -0
- package/templates/invoice/settings.json +1 -4
- package/templates/kilic.dev/Template.svelte +102 -0
- package/templates/kilic.dev/settings.json +1 -3
- package/templates/mail/Template.svelte +166 -0
- package/templates/mail/settings.json +1 -3
- package/dist/hooks/not-found.hook.mjs +0 -7
- package/dist/hooks/update-notifier.hook.mjs +0 -7
- package/dist/interfaces/pupeteer.mjs +0 -1
- package/templates/default/main.css +0 -73
- package/templates/invoice/.gitignore +0 -1
- package/templates/invoice/footer.html +0 -18
- package/templates/invoice/tailwind.css +0 -164
- package/templates/invoice/template.html.j2 +0 -226
- package/templates/kilic.dev/footer.html +0 -26
- package/templates/kilic.dev/header.html +0 -16
- package/templates/kilic.dev/main.css +0 -74
- package/templates/mail/.gitignore +0 -1
- package/templates/mail/tailwind.css +0 -145
- package/templates/mail/template.html.j2 +0 -14
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
Shell page of the live preview: the HTML and the printed PDF side by side, updated over server-sent events.
|
|
4
|
+
-->
|
|
5
|
+
<html lang="en">
|
|
6
|
+
<head>
|
|
7
|
+
<meta charset="utf-8" />
|
|
8
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
9
|
+
<title>md-printer preview</title>
|
|
10
|
+
<style>
|
|
11
|
+
:root {
|
|
12
|
+
color-scheme: light dark;
|
|
13
|
+
--bg: #f4f4f5;
|
|
14
|
+
--panel: #ffffff;
|
|
15
|
+
--text: #18181b;
|
|
16
|
+
--muted: #71717a;
|
|
17
|
+
--accent: #cd0043;
|
|
18
|
+
--border: #e4e4e7;
|
|
19
|
+
}
|
|
20
|
+
@media (prefers-color-scheme: dark) {
|
|
21
|
+
:root {
|
|
22
|
+
--bg: #17191e;
|
|
23
|
+
--panel: #22282f;
|
|
24
|
+
--text: #e4e4e7;
|
|
25
|
+
--muted: #979eab;
|
|
26
|
+
--border: #2c333d;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
* {
|
|
30
|
+
box-sizing: border-box;
|
|
31
|
+
}
|
|
32
|
+
body {
|
|
33
|
+
margin: 0;
|
|
34
|
+
height: 100vh;
|
|
35
|
+
display: grid;
|
|
36
|
+
grid-template-rows: auto 1fr;
|
|
37
|
+
font:
|
|
38
|
+
13px system-ui,
|
|
39
|
+
sans-serif;
|
|
40
|
+
background: var(--bg);
|
|
41
|
+
color: var(--text);
|
|
42
|
+
}
|
|
43
|
+
header {
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
gap: 12px;
|
|
47
|
+
padding: 8px 16px;
|
|
48
|
+
background: var(--panel);
|
|
49
|
+
border-bottom: 1px solid var(--border);
|
|
50
|
+
}
|
|
51
|
+
header strong {
|
|
52
|
+
margin-right: auto;
|
|
53
|
+
}
|
|
54
|
+
nav button {
|
|
55
|
+
padding: 4px 12px;
|
|
56
|
+
border: 1px solid var(--border);
|
|
57
|
+
background: transparent;
|
|
58
|
+
color: inherit;
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
}
|
|
61
|
+
nav button:first-child {
|
|
62
|
+
border-radius: 6px 0 0 6px;
|
|
63
|
+
}
|
|
64
|
+
nav button:last-child {
|
|
65
|
+
border-radius: 0 6px 6px 0;
|
|
66
|
+
}
|
|
67
|
+
nav button[aria-pressed='true'] {
|
|
68
|
+
background: var(--text);
|
|
69
|
+
color: var(--bg);
|
|
70
|
+
}
|
|
71
|
+
#status {
|
|
72
|
+
color: var(--muted);
|
|
73
|
+
font-variant-numeric: tabular-nums;
|
|
74
|
+
}
|
|
75
|
+
#badge {
|
|
76
|
+
display: none;
|
|
77
|
+
padding: 2px 8px;
|
|
78
|
+
border-radius: 999px;
|
|
79
|
+
background: var(--accent);
|
|
80
|
+
color: #fff;
|
|
81
|
+
font-weight: 600;
|
|
82
|
+
}
|
|
83
|
+
main {
|
|
84
|
+
display: grid;
|
|
85
|
+
gap: 1px;
|
|
86
|
+
background: var(--border);
|
|
87
|
+
min-height: 0;
|
|
88
|
+
}
|
|
89
|
+
main[data-view='html'] #pdf,
|
|
90
|
+
main[data-view='pdf'] #html {
|
|
91
|
+
display: none;
|
|
92
|
+
}
|
|
93
|
+
main[data-view='split'] {
|
|
94
|
+
grid-template-columns: 1fr 1fr;
|
|
95
|
+
}
|
|
96
|
+
section {
|
|
97
|
+
position: relative;
|
|
98
|
+
min-height: 0;
|
|
99
|
+
background: var(--panel);
|
|
100
|
+
}
|
|
101
|
+
iframe {
|
|
102
|
+
position: absolute;
|
|
103
|
+
inset: 0;
|
|
104
|
+
width: 100%;
|
|
105
|
+
height: 100%;
|
|
106
|
+
border: 0;
|
|
107
|
+
background: #fff;
|
|
108
|
+
}
|
|
109
|
+
iframe[hidden] {
|
|
110
|
+
visibility: hidden;
|
|
111
|
+
}
|
|
112
|
+
#overlay {
|
|
113
|
+
display: none;
|
|
114
|
+
position: fixed;
|
|
115
|
+
inset: 48px 16px auto;
|
|
116
|
+
max-height: 70vh;
|
|
117
|
+
overflow: auto;
|
|
118
|
+
padding: 16px;
|
|
119
|
+
border-left: 4px solid var(--accent);
|
|
120
|
+
border-radius: 8px;
|
|
121
|
+
background: var(--panel);
|
|
122
|
+
box-shadow: 0 8px 32px rgb(0 0 0 / 30%);
|
|
123
|
+
white-space: pre-wrap;
|
|
124
|
+
font:
|
|
125
|
+
12px ui-monospace,
|
|
126
|
+
monospace;
|
|
127
|
+
}
|
|
128
|
+
@media (max-width: 900px) {
|
|
129
|
+
main[data-view='split'] {
|
|
130
|
+
grid-template-columns: 1fr;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
</style>
|
|
134
|
+
</head>
|
|
135
|
+
<body>
|
|
136
|
+
<header>
|
|
137
|
+
<strong>md-printer preview</strong>
|
|
138
|
+
<span id="badge">unsigned preview</span>
|
|
139
|
+
<span id="status">connecting</span>
|
|
140
|
+
<nav>
|
|
141
|
+
<button data-view="html">HTML</button><button data-view="split">Split</button><button data-view="pdf">PDF</button>
|
|
142
|
+
</nav>
|
|
143
|
+
</header>
|
|
144
|
+
<main data-view="split">
|
|
145
|
+
<section id="html"><iframe title="HTML"></iframe><iframe title="HTML" hidden></iframe></section>
|
|
146
|
+
<section id="pdf"><iframe title="PDF"></iframe></section>
|
|
147
|
+
</main>
|
|
148
|
+
<pre id="overlay"></pre>
|
|
149
|
+
<script>
|
|
150
|
+
const main = document.querySelector('main')
|
|
151
|
+
const status = document.getElementById('status')
|
|
152
|
+
const badge = document.getElementById('badge')
|
|
153
|
+
const overlay = document.getElementById('overlay')
|
|
154
|
+
const pdf = document.querySelector('#pdf iframe')
|
|
155
|
+
let [visible, buffer] = document.querySelectorAll('#html iframe')
|
|
156
|
+
let pdfOutput = true
|
|
157
|
+
|
|
158
|
+
const setView = (view) => {
|
|
159
|
+
main.dataset.view = pdfOutput ? view : 'html'
|
|
160
|
+
for (const button of document.querySelectorAll('nav button')) {
|
|
161
|
+
button.setAttribute('aria-pressed', String(button.dataset.view === main.dataset.view))
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
localStorage.setItem('md-printer:view', view)
|
|
165
|
+
} catch {}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
for (const button of document.querySelectorAll('nav button')) {
|
|
169
|
+
button.addEventListener('click', () => setView(button.dataset.view))
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const currentPage = () => {
|
|
173
|
+
const doc = visible.contentDocument?.documentElement
|
|
174
|
+
const pages = Number(pdf.dataset.pages || 1)
|
|
175
|
+
|
|
176
|
+
if (!doc || doc.scrollHeight <= doc.clientHeight) {
|
|
177
|
+
return 1
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return Math.min(pages, Math.floor((doc.scrollTop / doc.scrollHeight) * pages) + 1)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const reloadHtml = (version) => {
|
|
184
|
+
const scroll = visible.contentDocument?.documentElement.scrollTop ?? 0
|
|
185
|
+
|
|
186
|
+
buffer.onload = () => {
|
|
187
|
+
buffer.contentDocument.documentElement.scrollTop = scroll
|
|
188
|
+
buffer.hidden = false
|
|
189
|
+
visible.hidden = true
|
|
190
|
+
;[visible, buffer] = [buffer, visible]
|
|
191
|
+
}
|
|
192
|
+
buffer.src = `/document.html?v=${version}`
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const events = new EventSource('/__events')
|
|
196
|
+
|
|
197
|
+
events.addEventListener('building', () => {
|
|
198
|
+
status.textContent = 'building'
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
events.addEventListener('ready', ({ data }) => {
|
|
202
|
+
const build = JSON.parse(data)
|
|
203
|
+
|
|
204
|
+
overlay.style.display = 'none'
|
|
205
|
+
status.textContent = `v${build.version}, ${build.pages ? `${build.pages} pages, ` : ''}${build.ms}ms`
|
|
206
|
+
badge.style.display = build.signed ? 'inline' : 'none'
|
|
207
|
+
pdfOutput = build.pdf
|
|
208
|
+
|
|
209
|
+
reloadHtml(build.version)
|
|
210
|
+
|
|
211
|
+
if (build.pdf) {
|
|
212
|
+
pdf.dataset.pages = build.pages
|
|
213
|
+
pdf.src = `/document.pdf?v=${build.version}#page=${currentPage()}`
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
setView(main.dataset.view)
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
events.addEventListener('failed', ({ data }) => {
|
|
220
|
+
const error = JSON.parse(data)
|
|
221
|
+
|
|
222
|
+
status.textContent = 'failed'
|
|
223
|
+
overlay.textContent = [error.file, error.message, error.frame].filter(Boolean).join('\n\n')
|
|
224
|
+
overlay.style.display = 'block'
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
events.addEventListener('error', () => {
|
|
228
|
+
status.textContent = 'disconnected'
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
let stored
|
|
232
|
+
try {
|
|
233
|
+
stored = localStorage.getItem('md-printer:view')
|
|
234
|
+
} catch {}
|
|
235
|
+
setView(stored ?? (window.innerWidth > 900 ? 'split' : 'pdf'))
|
|
236
|
+
</script>
|
|
237
|
+
</body>
|
|
238
|
+
</html>
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import "../../constants/file.constants.mjs";
|
|
2
|
+
import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
|
|
3
|
+
import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
|
|
4
|
+
import { resolveConfined } from "../template/assets.mjs";
|
|
5
|
+
import { isLoopbackRequest } from "../loopback.mjs";
|
|
6
|
+
import { TemplateService } from "../template/template.service.mjs";
|
|
7
|
+
import { Injectable } from "@nestjs/common";
|
|
8
|
+
import { mkdtemp, rm } from "fs/promises";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
import { LoggerService } from "@cenk1cenk2/oclif-common";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
import { tmpdir } from "os";
|
|
13
|
+
import { spawn } from "child_process";
|
|
14
|
+
import { once } from "events";
|
|
15
|
+
import { createServer } from "http";
|
|
16
|
+
import serveStatic from "serve-static";
|
|
17
|
+
//#region src/services/session/preview.service.ts
|
|
18
|
+
let PreviewService = class PreviewService {
|
|
19
|
+
logger;
|
|
20
|
+
template;
|
|
21
|
+
server;
|
|
22
|
+
clients = /* @__PURE__ */ new Set();
|
|
23
|
+
build;
|
|
24
|
+
error;
|
|
25
|
+
profile;
|
|
26
|
+
window;
|
|
27
|
+
constructor(logger, template) {
|
|
28
|
+
this.logger = logger;
|
|
29
|
+
this.template = template;
|
|
30
|
+
this.logger.setup(this.constructor.name);
|
|
31
|
+
}
|
|
32
|
+
async listen(options) {
|
|
33
|
+
const shell = await this.template.page(fileURLToPath(new URL("./Preview.svelte", import.meta.url)));
|
|
34
|
+
this.server = createServer((req, res) => {
|
|
35
|
+
this.handle(req, res, shell).catch((err) => {
|
|
36
|
+
this.logger.debug("Preview request failed: %s", err);
|
|
37
|
+
res.writeHead(500);
|
|
38
|
+
res.end();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
this.server.listen(options.port, options.host);
|
|
42
|
+
await once(this.server, "listening");
|
|
43
|
+
const { port } = this.server.address();
|
|
44
|
+
return `http://${options.host.includes(":") ? `[${options.host}]` : options.host}:${port}/`;
|
|
45
|
+
}
|
|
46
|
+
building() {
|
|
47
|
+
this.send("building", {});
|
|
48
|
+
}
|
|
49
|
+
ready(build) {
|
|
50
|
+
this.build = build;
|
|
51
|
+
this.error = void 0;
|
|
52
|
+
this.send("ready", this.summary(build));
|
|
53
|
+
}
|
|
54
|
+
failed(err) {
|
|
55
|
+
this.error = {
|
|
56
|
+
message: err.message,
|
|
57
|
+
frame: err.frame,
|
|
58
|
+
file: err.filename
|
|
59
|
+
};
|
|
60
|
+
this.send("failed", this.error);
|
|
61
|
+
}
|
|
62
|
+
async open(executable, url) {
|
|
63
|
+
this.profile = await mkdtemp(join(tmpdir(), "md-printer-preview-"));
|
|
64
|
+
this.window = spawn(executable, [
|
|
65
|
+
`--app=${url}`,
|
|
66
|
+
`--user-data-dir=${this.profile}`,
|
|
67
|
+
"--no-first-run",
|
|
68
|
+
"--no-default-browser-check"
|
|
69
|
+
], { stdio: "ignore" });
|
|
70
|
+
await new Promise((done) => {
|
|
71
|
+
this.window.on("exit", () => done());
|
|
72
|
+
this.window.on("error", (err) => {
|
|
73
|
+
this.logger.warn("Can not open the preview window, open it manually: %s (%s)", url, err.message);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
async onModuleDestroy() {
|
|
78
|
+
await this.close();
|
|
79
|
+
}
|
|
80
|
+
async close() {
|
|
81
|
+
const { server, window, profile } = this;
|
|
82
|
+
this.server = void 0;
|
|
83
|
+
this.window = void 0;
|
|
84
|
+
this.profile = void 0;
|
|
85
|
+
for (const client of this.clients) client.end();
|
|
86
|
+
this.clients.clear();
|
|
87
|
+
if (window && window.exitCode === null && window.signalCode === null && window.kill()) await once(window, "exit");
|
|
88
|
+
if (server?.listening) {
|
|
89
|
+
server.close();
|
|
90
|
+
await once(server, "close");
|
|
91
|
+
}
|
|
92
|
+
if (profile) await rm(profile, {
|
|
93
|
+
recursive: true,
|
|
94
|
+
force: true
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async handle(req, res, shell) {
|
|
98
|
+
if (!isLoopbackRequest(req)) {
|
|
99
|
+
res.writeHead(403);
|
|
100
|
+
res.end();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
switch (new URL(req.url, "http://localhost").pathname) {
|
|
104
|
+
case "/":
|
|
105
|
+
res.writeHead(200, {
|
|
106
|
+
["content-type"]: "text/html; charset=utf-8",
|
|
107
|
+
["cache-control"]: "no-store"
|
|
108
|
+
});
|
|
109
|
+
res.end(shell);
|
|
110
|
+
return;
|
|
111
|
+
case "/__events":
|
|
112
|
+
res.writeHead(200, {
|
|
113
|
+
["content-type"]: "text/event-stream",
|
|
114
|
+
["cache-control"]: "no-store",
|
|
115
|
+
connection: "keep-alive"
|
|
116
|
+
});
|
|
117
|
+
res.write("retry: 1000\n\n");
|
|
118
|
+
this.clients.add(res);
|
|
119
|
+
req.on("close", () => this.clients.delete(res));
|
|
120
|
+
if (this.error) res.write(this.event("failed", this.error));
|
|
121
|
+
else if (this.build) res.write(this.event("ready", this.summary(this.build)));
|
|
122
|
+
return;
|
|
123
|
+
case "/document.html":
|
|
124
|
+
if (!this.build) {
|
|
125
|
+
res.writeHead(404);
|
|
126
|
+
res.end();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
res.writeHead(200, {
|
|
130
|
+
["content-type"]: "text/html; charset=utf-8",
|
|
131
|
+
["cache-control"]: "no-store"
|
|
132
|
+
});
|
|
133
|
+
res.end(this.build.document.html);
|
|
134
|
+
return;
|
|
135
|
+
case "/document.pdf":
|
|
136
|
+
if (this.build?.ctx.options.output !== "pdf") {
|
|
137
|
+
res.writeHead(404);
|
|
138
|
+
res.end();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
res.writeHead(200, {
|
|
142
|
+
["content-type"]: "application/pdf",
|
|
143
|
+
["cache-control"]: "no-store"
|
|
144
|
+
});
|
|
145
|
+
res.end(this.build.output);
|
|
146
|
+
return;
|
|
147
|
+
default: return this.asset(req, res);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async asset(req, res) {
|
|
151
|
+
const reject = (status = 404) => {
|
|
152
|
+
res.writeHead(status);
|
|
153
|
+
res.end();
|
|
154
|
+
};
|
|
155
|
+
if (!this.build) return reject();
|
|
156
|
+
const base = this.build.ctx.input.directory;
|
|
157
|
+
const url = URL.parse(req.url, "file:///");
|
|
158
|
+
const path = url && join(base, fileURLToPath(url));
|
|
159
|
+
if (!path || !await resolveConfined(path, base)) return reject();
|
|
160
|
+
serveStatic(base, {
|
|
161
|
+
dotfiles: "ignore",
|
|
162
|
+
index: false,
|
|
163
|
+
fallthrough: false,
|
|
164
|
+
cacheControl: false,
|
|
165
|
+
setHeaders: (response) => response.setHeader("cache-control", "no-store")
|
|
166
|
+
})(req, res, (err) => reject(err?.status));
|
|
167
|
+
}
|
|
168
|
+
summary(build) {
|
|
169
|
+
const pdf = build.ctx.options.output === "pdf";
|
|
170
|
+
return {
|
|
171
|
+
version: build.version,
|
|
172
|
+
ms: build.ms,
|
|
173
|
+
pdf,
|
|
174
|
+
pages: pdf ? (build.output.toString("latin1").match(/\/Type\s*\/Page[^s]/g) ?? []).length : 0,
|
|
175
|
+
signed: Boolean(build.ctx.signature)
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
send(event, data) {
|
|
179
|
+
for (const client of this.clients) client.write(this.event(event, data));
|
|
180
|
+
}
|
|
181
|
+
event(event, data) {
|
|
182
|
+
return `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
PreviewService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof LoggerService === "undefined" ? Object : LoggerService, typeof TemplateService === "undefined" ? Object : TemplateService])], PreviewService);
|
|
186
|
+
//#endregion
|
|
187
|
+
export { PreviewService };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import "../../constants/template.constants.mjs";
|
|
2
|
+
import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
|
|
3
|
+
import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
|
|
4
|
+
import { PrinterService } from "../printer.service.mjs";
|
|
5
|
+
import { Injectable } from "@nestjs/common";
|
|
6
|
+
import { dirname, join, resolve } from "path";
|
|
7
|
+
import { LoggerService } from "@cenk1cenk2/oclif-common";
|
|
8
|
+
import { EventEmitter } from "events";
|
|
9
|
+
import { watch } from "chokidar";
|
|
10
|
+
import { debounce } from "perfect-debounce";
|
|
11
|
+
//#region src/services/session/session.service.ts
|
|
12
|
+
let SessionService = class SessionService extends EventEmitter {
|
|
13
|
+
logger;
|
|
14
|
+
printer;
|
|
15
|
+
watcher;
|
|
16
|
+
bundle;
|
|
17
|
+
stopped = false;
|
|
18
|
+
modules = /* @__PURE__ */ new Set();
|
|
19
|
+
watched = /* @__PURE__ */ new Set();
|
|
20
|
+
version = 0;
|
|
21
|
+
building;
|
|
22
|
+
flags;
|
|
23
|
+
ignored = /* @__PURE__ */ new Set();
|
|
24
|
+
build = debounce(() => this.stopped ? Promise.resolve() : this.building = this.run(), 50);
|
|
25
|
+
constructor(logger, printer) {
|
|
26
|
+
super();
|
|
27
|
+
this.logger = logger;
|
|
28
|
+
this.printer = printer;
|
|
29
|
+
this.logger.setup(this.constructor.name);
|
|
30
|
+
}
|
|
31
|
+
async start(flags) {
|
|
32
|
+
this.flags = flags;
|
|
33
|
+
this.watcher = watch([], {
|
|
34
|
+
ignoreInitial: true,
|
|
35
|
+
atomic: true
|
|
36
|
+
}).on("change", (path) => this.changed(path)).on("add", (path) => this.changed(path)).on("unlink", (path) => this.changed(path));
|
|
37
|
+
await this.build();
|
|
38
|
+
}
|
|
39
|
+
async onModuleDestroy() {
|
|
40
|
+
await this.stop();
|
|
41
|
+
}
|
|
42
|
+
ignore(path) {
|
|
43
|
+
this.ignored.add(resolve(path));
|
|
44
|
+
}
|
|
45
|
+
async stop() {
|
|
46
|
+
this.stopped = true;
|
|
47
|
+
this.build.cancel();
|
|
48
|
+
await this.building;
|
|
49
|
+
await this.watcher?.close();
|
|
50
|
+
this.watcher = void 0;
|
|
51
|
+
}
|
|
52
|
+
changed(path) {
|
|
53
|
+
const file = resolve(path);
|
|
54
|
+
if (this.ignored.has(file)) return;
|
|
55
|
+
this.logger.debug("Changed: %s", file);
|
|
56
|
+
if (this.modules.has(file) || this.bundle && dirname(file) === join(this.bundle.template, "components")) this.bundle = void 0;
|
|
57
|
+
this.build();
|
|
58
|
+
}
|
|
59
|
+
async run() {
|
|
60
|
+
const started = performance.now();
|
|
61
|
+
this.emit("building");
|
|
62
|
+
try {
|
|
63
|
+
const input = await this.printer.input(this.flags);
|
|
64
|
+
const options = await this.printer.options(this.flags, input);
|
|
65
|
+
const template = join(options.template, "Template.svelte");
|
|
66
|
+
this.modules.add(template);
|
|
67
|
+
this.watch([
|
|
68
|
+
input.file,
|
|
69
|
+
template,
|
|
70
|
+
join(options.template, "settings.json"),
|
|
71
|
+
join(options.template, "i18n"),
|
|
72
|
+
join(options.template, "components"),
|
|
73
|
+
options.sign.certificate && resolve(options.sign.certificate)
|
|
74
|
+
]);
|
|
75
|
+
if (this.bundle?.template !== options.template) this.bundle = {
|
|
76
|
+
template: options.template,
|
|
77
|
+
bundle: this.printer.bundle(options)
|
|
78
|
+
};
|
|
79
|
+
const cached = this.bundle;
|
|
80
|
+
const bundle = await cached.bundle.catch((err) => {
|
|
81
|
+
if (this.bundle === cached) this.bundle = void 0;
|
|
82
|
+
throw err;
|
|
83
|
+
});
|
|
84
|
+
if (this.bundle !== cached) return;
|
|
85
|
+
this.modules = /* @__PURE__ */ new Set([template, ...bundle.modules]);
|
|
86
|
+
this.watch(bundle.modules);
|
|
87
|
+
const ctx = {
|
|
88
|
+
input,
|
|
89
|
+
options
|
|
90
|
+
};
|
|
91
|
+
await this.printer.prepare(ctx);
|
|
92
|
+
const document = await this.printer.document(ctx, bundle);
|
|
93
|
+
this.watch(document.dependencies);
|
|
94
|
+
const { content, assets } = await this.printer.output(ctx, document);
|
|
95
|
+
this.watch(assets);
|
|
96
|
+
this.emit("ready", {
|
|
97
|
+
version: ++this.version,
|
|
98
|
+
ms: Math.round(performance.now() - started),
|
|
99
|
+
ctx,
|
|
100
|
+
document,
|
|
101
|
+
output: content
|
|
102
|
+
});
|
|
103
|
+
} catch (err) {
|
|
104
|
+
this.emit("failed", err instanceof Error ? err : new Error(String(err)));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
watch(paths) {
|
|
108
|
+
const fresh = paths.filter((path) => path && !this.watched.has(path));
|
|
109
|
+
for (const path of fresh) this.watched.add(path);
|
|
110
|
+
if (fresh.length) this.watcher.add(fresh);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
SessionService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof LoggerService === "undefined" ? Object : LoggerService, typeof PrinterService === "undefined" ? Object : PrinterService])], SessionService);
|
|
114
|
+
//#endregion
|
|
115
|
+
export { SessionService };
|
|
@@ -1,48 +1,44 @@
|
|
|
1
|
+
import "../../../constants/signature.constants.mjs";
|
|
1
2
|
import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
|
|
2
3
|
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
|
|
4
|
+
import { isLoopbackRequest } from "../../loopback.mjs";
|
|
3
5
|
import { SignatureAdapter } from "../signature.adapter.mjs";
|
|
4
|
-
import { LoggerService } from "@cenk1cenk2/oclif-common";
|
|
5
6
|
import { Injectable } from "@nestjs/common";
|
|
6
|
-
import { spawn } from "child_process";
|
|
7
7
|
import { readFile } from "fs/promises";
|
|
8
|
+
import { CommandContext, LoggerService } from "@cenk1cenk2/oclif-common";
|
|
9
|
+
import { spawn } from "child_process";
|
|
10
|
+
import { once } from "events";
|
|
8
11
|
import { createServer } from "http";
|
|
9
|
-
|
|
12
|
+
import getRawBody from "raw-body";
|
|
10
13
|
//#region src/services/signature/adapters/a-trust.adapter.ts
|
|
11
|
-
var _ATrustAdapter;
|
|
12
14
|
let ATrustAdapter = class ATrustAdapter extends SignatureAdapter {
|
|
13
|
-
static {
|
|
14
|
-
_ATrustAdapter = this;
|
|
15
|
-
}
|
|
16
15
|
logger;
|
|
17
|
-
|
|
18
|
-
static NAMESPACE = "http://www.buergerkarte.at/namespaces/securitylayer/1.2#";
|
|
19
|
-
static TIMEOUT = 6e5;
|
|
20
|
-
static PAGE = "./a-trust.html";
|
|
16
|
+
context;
|
|
21
17
|
name = "a-trust";
|
|
22
|
-
constructor(logger) {
|
|
18
|
+
constructor(logger, context) {
|
|
23
19
|
super();
|
|
24
20
|
this.logger = logger;
|
|
21
|
+
this.context = context;
|
|
25
22
|
this.logger.setup(this.constructor.name);
|
|
26
23
|
}
|
|
27
24
|
async sign(request) {
|
|
28
25
|
return this.collect(await this.session(this.request(request)));
|
|
29
26
|
}
|
|
30
27
|
request({ pdf, excluded: [from, to] }) {
|
|
31
|
-
const data = Buffer.from(pdf).fill(0, from, to + 1);
|
|
32
28
|
return [
|
|
33
29
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
|
|
34
|
-
|
|
30
|
+
"<sl:CreateCMSSignatureRequest xmlns:sl=\"http://www.buergerkarte.at/namespaces/securitylayer/1.2#\" Structure=\"detached\" PAdESCompatibility=\"true\">",
|
|
35
31
|
"<sl:KeyboxIdentifier>SecureSignatureKeypair</sl:KeyboxIdentifier>",
|
|
36
32
|
"<sl:DataObject>",
|
|
37
33
|
"<sl:MetaInfo><sl:MimeType>application/pdf</sl:MimeType></sl:MetaInfo>",
|
|
38
|
-
`<sl:Content><sl:Base64Content>${
|
|
34
|
+
`<sl:Content><sl:Base64Content>${Buffer.from(pdf).fill(0, from, to + 1).toString("base64")}</sl:Base64Content></sl:Content>`,
|
|
39
35
|
`<sl:ExcludedByteRange><sl:From>${from}</sl:From><sl:To>${to}</sl:To></sl:ExcludedByteRange>`,
|
|
40
36
|
"</sl:DataObject>",
|
|
41
37
|
"</sl:CreateCMSSignatureRequest>"
|
|
42
38
|
].join("");
|
|
43
39
|
}
|
|
44
40
|
async session(request) {
|
|
45
|
-
let url =
|
|
41
|
+
let url = "https://service.a-trust.at/mobile/https-security-layer-request/default.aspx";
|
|
46
42
|
let response = await fetch(url, {
|
|
47
43
|
method: "POST",
|
|
48
44
|
body: new URLSearchParams([["XMLRequest", request]]),
|
|
@@ -63,8 +59,12 @@ let ATrustAdapter = class ATrustAdapter extends SignatureAdapter {
|
|
|
63
59
|
resolve = done;
|
|
64
60
|
reject = fail;
|
|
65
61
|
});
|
|
66
|
-
const page = await readFile(new URL(
|
|
62
|
+
const page = await readFile(new URL("./a-trust.html", import.meta.url));
|
|
67
63
|
const server = createServer(async (req, res) => {
|
|
64
|
+
if (!isLoopbackRequest(req, req.method === "POST" ? `http://127.0.0.1:${server.address().port}` : void 0)) {
|
|
65
|
+
res.writeHead(403);
|
|
66
|
+
return res.end();
|
|
67
|
+
}
|
|
68
68
|
if (req.method === "GET" && req.url === "/session") {
|
|
69
69
|
res.setHeader("content-type", "application/json");
|
|
70
70
|
res.writeHead(200);
|
|
@@ -75,8 +75,14 @@ let ATrustAdapter = class ATrustAdapter extends SignatureAdapter {
|
|
|
75
75
|
res.writeHead(200);
|
|
76
76
|
return res.end(page);
|
|
77
77
|
}
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
const body = await getRawBody(req, {
|
|
79
|
+
limit: "1mb",
|
|
80
|
+
encoding: true
|
|
81
|
+
}).catch((err) => {
|
|
82
|
+
res.writeHead(err.status ?? 400);
|
|
83
|
+
res.end();
|
|
84
|
+
});
|
|
85
|
+
if (body === void 0) return;
|
|
80
86
|
const cms = (body.match(/<(?:\w+:)?CMSSignature>([^<]+)</)?.[1] ?? body).replace(/\s+/g, "");
|
|
81
87
|
if (!/^MII[A-Za-z0-9+/=]+$/.test(cms)) {
|
|
82
88
|
res.writeHead(400);
|
|
@@ -86,31 +92,21 @@ let ATrustAdapter = class ATrustAdapter extends SignatureAdapter {
|
|
|
86
92
|
res.end("Signature received, you can close this tab.");
|
|
87
93
|
resolve(Buffer.from(cms, "base64"));
|
|
88
94
|
});
|
|
89
|
-
|
|
95
|
+
server.listen(0, "127.0.0.1");
|
|
96
|
+
await once(server, "listening");
|
|
90
97
|
const url = `http://127.0.0.1:${server.address().port}/`;
|
|
91
98
|
this.logger.info("Sign the document in the browser, then paste the signature back: %s", url);
|
|
92
99
|
this.open(url);
|
|
93
|
-
|
|
94
|
-
return result.finally(() =>
|
|
95
|
-
clearTimeout(timeout);
|
|
96
|
-
server.close();
|
|
97
|
-
});
|
|
100
|
+
this.context.signal.addEventListener("abort", () => reject(/* @__PURE__ */ new Error("Signing is aborted.")), { once: true });
|
|
101
|
+
return result.finally(() => server.close());
|
|
98
102
|
}
|
|
99
103
|
open(url) {
|
|
100
|
-
|
|
101
|
-
"cmd",
|
|
102
|
-
"/c",
|
|
103
|
-
"start",
|
|
104
|
-
"",
|
|
105
|
-
url
|
|
106
|
-
] : ["xdg-open", url];
|
|
107
|
-
spawn(command, args, {
|
|
104
|
+
spawn("xdg-open", [url], {
|
|
108
105
|
stdio: "ignore",
|
|
109
106
|
detached: true
|
|
110
107
|
}).on("error", (err) => this.logger.warn("Can not open the browser, open the link manually: %s", err.message)).unref();
|
|
111
108
|
}
|
|
112
109
|
};
|
|
113
|
-
ATrustAdapter =
|
|
114
|
-
|
|
110
|
+
ATrustAdapter = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof LoggerService === "undefined" ? Object : LoggerService, typeof CommandContext === "undefined" ? Object : CommandContext])], ATrustAdapter);
|
|
115
111
|
//#endregion
|
|
116
|
-
export { ATrustAdapter };
|
|
112
|
+
export { ATrustAdapter };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { SignatureAdapter } from "./signature.adapter.mjs";
|
|
2
2
|
import { ATrustAdapter } from "./adapters/a-trust.adapter.mjs";
|
|
3
3
|
import { SignatureService } from "./signature.service.mjs";
|
|
4
|
-
|
|
5
|
-
export { ATrustAdapter, SignatureAdapter, SignatureService };
|
|
4
|
+
export { ATrustAdapter, SignatureAdapter, SignatureService };
|