@skgo/sveltekit-adapter 0.13.1 → 0.14.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/package.json +1 -1
- package/skgo-adapter/entry.js +115 -62
- package/skgo-adapter/env.js +1 -0
- package/skgo-adapter/polyfill.js +12 -11
package/package.json
CHANGED
package/skgo-adapter/entry.js
CHANGED
|
@@ -24,6 +24,7 @@ import * as devalue from 'skgo:devalue';
|
|
|
24
24
|
import { decoders, init_transport, parse } from 'skgo:kit/transport';
|
|
25
25
|
import { components, universalLoads } from 'skgo:nodes';
|
|
26
26
|
import { transport } from 'skgo:hooks';
|
|
27
|
+
import { serialize_data } from 'skgo:kit/serialize-data';
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* The app's transport hook, installed the way kit installs it
|
|
@@ -76,89 +77,140 @@ function make_state() {
|
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
/**
|
|
80
|
-
* event.fetch during a render. Kit's own (runtime/server/fetch.js) resolves a
|
|
81
|
-
* relative URL against the page's own origin, forwards the incoming cookies
|
|
82
|
-
* on a same-origin request unless credentials is "omit", and otherwise hands
|
|
83
|
-
* the request to a real fetch. This engine has no socket to hand a
|
|
84
|
-
* cross-origin request to — no application I/O executes in JavaScript here —
|
|
85
|
-
* so a same-origin URL is a call back into Go's own handler for it, exactly
|
|
86
|
-
* the shape a remote function's call is, and a cross-origin one is refused: a
|
|
87
|
-
* thrown TypeError, before anything is dispatched. (Kit's own version also
|
|
88
|
-
* forwards the page's own Authorization header on a same-origin request; this
|
|
89
|
-
* one does not, because the render's own request never reaches the engine
|
|
90
|
-
* with its headers — only its cookies, in req.cookies.)
|
|
91
|
-
*
|
|
92
|
-
* @param {any} req
|
|
93
|
-
* @param {URL} url
|
|
94
|
-
*/
|
|
80
|
+
/** All render-time I/O is dispatched by Go, including the handleFetch hook. */
|
|
95
81
|
function create_fetch(req, url) {
|
|
96
82
|
return async function (input, init) {
|
|
97
|
-
const request = input instanceof Request ? input : new Request(input, init);
|
|
83
|
+
const request = input instanceof Request ? input : new Request(new URL(input, url).href, init);
|
|
98
84
|
const target = new URL(request.url, url);
|
|
99
|
-
|
|
100
|
-
if (!same_origin(target, url)) {
|
|
101
|
-
throw new TypeError(
|
|
102
|
-
"skgo: event.fetch only reaches this app's own routes during server-side rendering (" +
|
|
103
|
-
origin_of(target) +
|
|
104
|
-
' is not ' +
|
|
105
|
-
origin_of(url) +
|
|
106
|
-
')'
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const credentials = init?.credentials ?? request.credentials ?? 'same-origin';
|
|
111
|
-
if (credentials !== 'omit') {
|
|
112
|
-
const cookie = Object.entries(req.cookies ?? {})
|
|
113
|
-
.map(([name, value]) => name + '=' + value)
|
|
114
|
-
.join('; ');
|
|
115
|
-
if (cookie) request.headers.set('cookie', cookie);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
85
|
const headers = {};
|
|
119
|
-
request.headers.forEach((value, name) => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
86
|
+
request.headers.forEach((value, name) => { headers[name] = value; });
|
|
87
|
+
const envelope = {
|
|
88
|
+
method: request.method || 'GET', url: target.href, headers,
|
|
89
|
+
credentials: request.credentials, mode: request.mode
|
|
90
|
+
};
|
|
124
91
|
if (typeof request._body === 'string') envelope.body = request._body;
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
const answer = JSON.parse(raw);
|
|
92
|
+
else if (request._body != null) throw new TypeError('skgo: render-time fetch request bodies must be text');
|
|
93
|
+
const answer = JSON.parse(await globalThis.__skgo_fetch(JSON.stringify(envelope)));
|
|
128
94
|
if (answer.error) throw new TypeError(answer.error);
|
|
95
|
+
const value = answer.response;
|
|
96
|
+
const body = value.bodyBase64
|
|
97
|
+
? Uint8Array.from(atob(value.bodyBase64), (c) => c.charCodeAt(0))
|
|
98
|
+
: value.body ?? '';
|
|
99
|
+
const response = new Response(body, { status: value.status, statusText: value.statusText, headers: value.headers });
|
|
100
|
+
response._serializedHeaders = value.serializedHeaders ?? {};
|
|
101
|
+
return response;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
129
104
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Kit's universal-load fetch contract (server/page/load_data.js): record bodies
|
|
107
|
+
* when consumed, hash the author's headers/body rather than inherited cookies,
|
|
108
|
+
* and reject access to headers hydration will not receive. The final HTML is
|
|
109
|
+
* emitted by Kit's own serialize_data, including escaping, hashes and TTLs.
|
|
110
|
+
*/
|
|
111
|
+
function create_universal_fetch(event, req, fetched) {
|
|
112
|
+
return async (input, init) => {
|
|
113
|
+
const request_headers = input instanceof Request
|
|
114
|
+
? ([...input.headers].length ? new Headers(input.headers) : undefined)
|
|
115
|
+
: init?.headers;
|
|
116
|
+
const request_body = input instanceof Request ? input._body : init?.body;
|
|
117
|
+
const url = new URL(input instanceof Request ? input.url : input, event.url);
|
|
118
|
+
let response = await event.fetch(input, init);
|
|
119
|
+
if (!same_origin(url, event.url) && (url.protocol === 'http:' || url.protocol === 'https:')) {
|
|
120
|
+
const mode = input instanceof Request ? input.mode : init?.mode ?? 'cors';
|
|
121
|
+
if (mode === 'no-cors') {
|
|
122
|
+
const allowed = response._serializedHeaders;
|
|
123
|
+
response = new Response('', { status: response.status, statusText: response.statusText, headers: response.headers });
|
|
124
|
+
response._serializedHeaders = allowed;
|
|
125
|
+
} else {
|
|
126
|
+
const acao = response.headers.get('access-control-allow-origin');
|
|
127
|
+
if (!acao || (acao !== '*' && acao !== origin_of(event.url))) {
|
|
128
|
+
throw new Error("CORS error: incorrect or missing 'Access-Control-Allow-Origin' header");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const cache_url = same_origin(url, event.url)
|
|
133
|
+
? url.pathname + url.search
|
|
134
|
+
: url.href;
|
|
135
|
+
const allowed = response._serializedHeaders;
|
|
136
|
+
const wrap = (target) => {
|
|
137
|
+
const record = (body, is_b64) => {
|
|
138
|
+
fetched.push({
|
|
139
|
+
url: cache_url, method: event.request.method,
|
|
140
|
+
request_headers, request_body, response: target,
|
|
141
|
+
response_body: body, is_b64, allowed
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
const text = async () => {
|
|
145
|
+
const body = await target.text();
|
|
146
|
+
if (body === '' && [101, 204, 205, 304].includes(target.status)) {
|
|
147
|
+
record(undefined, false);
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
record(body, false);
|
|
151
|
+
return body;
|
|
152
|
+
};
|
|
153
|
+
const headers = new Headers(target.headers);
|
|
154
|
+
const get = headers.get.bind(headers);
|
|
155
|
+
if (req.csr) {
|
|
156
|
+
headers.get = (key) => {
|
|
157
|
+
const lower = key.toLowerCase();
|
|
158
|
+
const value = get(lower);
|
|
159
|
+
if (value && !lower.startsWith('x-sveltekit-') && !(lower in allowed)) {
|
|
160
|
+
throw new Error(`Failed to get response header "${lower}" — it must be included by the filterSerializedResponseHeaders option (at ${event.route.id})`);
|
|
161
|
+
}
|
|
162
|
+
return value;
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return new Proxy(target, {
|
|
166
|
+
get(target, key) {
|
|
167
|
+
if (key === 'headers') return headers;
|
|
168
|
+
if (key === 'text') return text;
|
|
169
|
+
if (key === 'json') return async () => { const body = await text(); return body ? JSON.parse(body) : undefined; };
|
|
170
|
+
if (key === 'arrayBuffer') return async () => {
|
|
171
|
+
const buffer = await target.arrayBuffer();
|
|
172
|
+
let binary = '';
|
|
173
|
+
for (const byte of new Uint8Array(buffer)) binary += String.fromCharCode(byte);
|
|
174
|
+
record(btoa(binary), true);
|
|
175
|
+
return buffer;
|
|
176
|
+
};
|
|
177
|
+
if (key === 'clone') return () => wrap(target.clone());
|
|
178
|
+
const value = Reflect.get(target, key, target);
|
|
179
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
return wrap(response);
|
|
136
184
|
};
|
|
137
185
|
}
|
|
138
186
|
|
|
187
|
+
|
|
139
188
|
/**
|
|
140
189
|
* Same-origin, spelled out rather than taken from `origin`. The engine's URL is
|
|
141
190
|
* Go's (goja_nodejs, over net/url) and its `origin` is scheme and hostname with
|
|
142
191
|
* the port left out, which would make a page on :8080 and a URL on :9999 look
|
|
143
192
|
* like the same site — and this is the one comparison standing between a
|
|
144
|
-
*
|
|
145
|
-
* port are each exactly what the standard compares.
|
|
193
|
+
* cached response and a foreign response. Protocol, hostname and
|
|
194
|
+
* port are each exactly what the standard compares. Go enforces dispatch separately.
|
|
146
195
|
*
|
|
147
196
|
* @param {URL} a
|
|
148
197
|
* @param {URL} b
|
|
149
198
|
*/
|
|
150
199
|
function same_origin(a, b) {
|
|
151
|
-
|
|
200
|
+
const port = (url) => url.port || (url.protocol === 'https:' ? '443' : '80');
|
|
201
|
+
return a.protocol === b.protocol && a.hostname === b.hostname && port(a) === port(b);
|
|
152
202
|
}
|
|
153
203
|
|
|
154
204
|
/**
|
|
155
205
|
* What `url.origin` would say if this engine's URL reported it the way the
|
|
156
|
-
* standard does
|
|
206
|
+
* standard does, used for CORS validation.
|
|
157
207
|
*
|
|
158
208
|
* @param {URL} url
|
|
159
209
|
*/
|
|
160
210
|
function origin_of(url) {
|
|
161
|
-
|
|
211
|
+
const default_port = (url.protocol === 'http:' && url.port === '80') || (url.protocol === 'https:' && url.port === '443');
|
|
212
|
+
const host = default_port ? url.host.replace(/:(80|443)$/, '') : url.host;
|
|
213
|
+
return host ? url.protocol + '//' + host : 'null';
|
|
162
214
|
}
|
|
163
215
|
|
|
164
216
|
/**
|
|
@@ -181,7 +233,7 @@ function make_event(req, url) {
|
|
|
181
233
|
locals: {},
|
|
182
234
|
params: req.params ?? {},
|
|
183
235
|
platform: undefined,
|
|
184
|
-
request: { headers:
|
|
236
|
+
request: { headers: new Headers(req.headers), method: req.method ?? 'GET' },
|
|
185
237
|
route: { id: req.route_id ?? null },
|
|
186
238
|
setHeaders: () => {},
|
|
187
239
|
url,
|
|
@@ -218,7 +270,7 @@ function node_data(node) {
|
|
|
218
270
|
});
|
|
219
271
|
}
|
|
220
272
|
|
|
221
|
-
async function build_props(req, url, event, state) {
|
|
273
|
+
async function build_props(req, url, event, state, fetched) {
|
|
222
274
|
const form = req.form ? devalue.parse(req.form, decoders) : null;
|
|
223
275
|
const page = {
|
|
224
276
|
error: req.error ?? null,
|
|
@@ -261,9 +313,7 @@ async function build_props(req, url, event, state) {
|
|
|
261
313
|
parent: async () => parent_data,
|
|
262
314
|
depends: () => {},
|
|
263
315
|
untrack: (fn) => fn(),
|
|
264
|
-
fetch: (
|
|
265
|
-
throw new Error('skgo: universal load fetch is not supported during rendering');
|
|
266
|
-
},
|
|
316
|
+
fetch: create_universal_fetch(event, req, fetched),
|
|
267
317
|
setHeaders: () => {
|
|
268
318
|
throw new Error('skgo: universal load setHeaders is not supported during rendering');
|
|
269
319
|
}
|
|
@@ -363,7 +413,8 @@ globalThis.__skgo_render = function (req_json) {
|
|
|
363
413
|
status: 200,
|
|
364
414
|
error: null,
|
|
365
415
|
head: '',
|
|
366
|
-
body: ''
|
|
416
|
+
body: '',
|
|
417
|
+
fetched: ''
|
|
367
418
|
};
|
|
368
419
|
|
|
369
420
|
try {
|
|
@@ -371,7 +422,8 @@ globalThis.__skgo_render = function (req_json) {
|
|
|
371
422
|
const url = new URL(req.url);
|
|
372
423
|
const state = make_state();
|
|
373
424
|
const event = make_event(req, url);
|
|
374
|
-
const
|
|
425
|
+
const fetched = [];
|
|
426
|
+
const props_promise = build_props(req, url, event, state, fetched);
|
|
375
427
|
const promise = props_promise.then((props) => {
|
|
376
428
|
result.status = props.page.status;
|
|
377
429
|
result.error = req.error ?? null;
|
|
@@ -417,6 +469,7 @@ globalThis.__skgo_render = function (req_json) {
|
|
|
417
469
|
(rendered) => {
|
|
418
470
|
result.head = rendered.head;
|
|
419
471
|
result.body = rendered.body;
|
|
472
|
+
result.fetched = fetched.map((item) => serialize_data(item, (key) => key in item.allowed)).join("\n\t\t\t");
|
|
420
473
|
result.done = true;
|
|
421
474
|
},
|
|
422
475
|
(err) => {
|
package/skgo-adapter/env.js
CHANGED
|
@@ -132,6 +132,7 @@ function kitAliases(root) {
|
|
|
132
132
|
'skgo:kit/remote': join(runtime, 'app/server/remote/index.js'),
|
|
133
133
|
'skgo:kit/shared': join(runtime, 'shared.js'),
|
|
134
134
|
'skgo:kit/transport': join(runtime, 'app/internal/transport.js'),
|
|
135
|
+
'skgo:kit/serialize-data': join(runtime, 'server/page/serialize_data.js'),
|
|
135
136
|
'skgo:kit/props': join(runtime, 'props.svelte.js'),
|
|
136
137
|
'skgo:kit/root': join(runtime, 'components/root.svelte'),
|
|
137
138
|
// kit's own `$app/paths` server implementation, which app-paths.js
|
package/skgo-adapter/polyfill.js
CHANGED
|
@@ -39,9 +39,9 @@ if (typeof globalThis.Headers === 'undefined') {
|
|
|
39
39
|
if (init instanceof Headers) {
|
|
40
40
|
for (const [k, v] of init._) this._.set(k, v);
|
|
41
41
|
} else if (Array.isArray(init)) {
|
|
42
|
-
for (const [k, v] of init) this.
|
|
42
|
+
for (const [k, v] of init) this.append(k, v);
|
|
43
43
|
} else if (init && typeof init === 'object') {
|
|
44
|
-
for (const k of Object.keys(init)) this.
|
|
44
|
+
for (const k of Object.keys(init)) this.append(k, init[k]);
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
set(name, value) {
|
|
@@ -66,19 +66,19 @@ if (typeof globalThis.Headers === 'undefined') {
|
|
|
66
66
|
this._.delete(String(name).toLowerCase());
|
|
67
67
|
}
|
|
68
68
|
forEach(fn, thisArg) {
|
|
69
|
-
for (const [k, v] of this.
|
|
69
|
+
for (const [k, v] of this.entries()) fn.call(thisArg, v, k, this);
|
|
70
70
|
}
|
|
71
71
|
keys() {
|
|
72
|
-
return this.
|
|
72
|
+
return Array.from(this.entries(), ([k]) => k)[Symbol.iterator]();
|
|
73
73
|
}
|
|
74
74
|
values() {
|
|
75
|
-
return this.
|
|
75
|
+
return Array.from(this.entries(), ([, v]) => v)[Symbol.iterator]();
|
|
76
76
|
}
|
|
77
77
|
entries() {
|
|
78
|
-
return this._.
|
|
78
|
+
return Array.from(this._).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)[Symbol.iterator]();
|
|
79
79
|
}
|
|
80
80
|
[Symbol.iterator]() {
|
|
81
|
-
return this.
|
|
81
|
+
return this.entries();
|
|
82
82
|
}
|
|
83
83
|
};
|
|
84
84
|
}
|
|
@@ -111,6 +111,7 @@ if (typeof globalThis.Request === 'undefined') {
|
|
|
111
111
|
}
|
|
112
112
|
async text() { return this._body ?? ''; }
|
|
113
113
|
async json() { return JSON.parse(this._body ?? 'null'); }
|
|
114
|
+
clone() { return new Request(this); }
|
|
114
115
|
};
|
|
115
116
|
}
|
|
116
117
|
|
|
@@ -123,11 +124,11 @@ if (typeof globalThis.Response === 'undefined') {
|
|
|
123
124
|
this.headers = init.headers instanceof Headers ? init.headers : new Headers(init.headers);
|
|
124
125
|
this.ok = this.status >= 200 && this.status < 300;
|
|
125
126
|
}
|
|
126
|
-
async text() { return this._body; }
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
async text() { return typeof this._body === 'string' ? this._body : new TextDecoder().decode(this._body); }
|
|
128
|
+
async json() { return JSON.parse(await this.text()); }
|
|
129
|
+
async arrayBuffer() { return (typeof this._body === 'string' ? new TextEncoder().encode(this._body) : new Uint8Array(this._body)).buffer; }
|
|
129
130
|
clone() {
|
|
130
|
-
return new Response(this._body, { status: this.status, statusText: this.statusText, headers: this.headers });
|
|
131
|
+
return new Response(this._body, { status: this.status, statusText: this.statusText, headers: new Headers(this.headers) });
|
|
131
132
|
}
|
|
132
133
|
};
|
|
133
134
|
}
|