@devvit/server 0.11.14-next-2025-05-05-765f3688c.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/LICENSE +26 -0
- package/README.md +3 -0
- package/index.d.ts +4 -0
- package/index.d.ts.map +1 -0
- package/index.js +3 -0
- package/package.json +42 -0
- package/request-context.d.ts +19 -0
- package/request-context.d.ts.map +1 -0
- package/request-context.js +23 -0
- package/request-context.test.d.ts.map +1 -0
- package/webbit-post.d.ts +45 -0
- package/webbit-post.d.ts.map +1 -0
- package/webbit-post.js +123 -0
- package/webbit-server.d.ts +4 -0
- package/webbit-server.d.ts.map +1 -0
- package/webbit-server.js +29 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Copyright (c) 2023 Reddit Inc.
|
|
2
|
+
|
|
3
|
+
Redistribution and use in source and binary forms, with or without
|
|
4
|
+
modification, are permitted provided that the following conditions
|
|
5
|
+
are met:
|
|
6
|
+
|
|
7
|
+
1. Redistributions of source code must retain the above copyright
|
|
8
|
+
notice, this list of conditions and the following disclaimer.
|
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
10
|
+
notice, this list of conditions and the following disclaimer in the
|
|
11
|
+
documentation and/or other materials provided with the distribution.
|
|
12
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
13
|
+
contributors may be used to endorse or promote products derived from
|
|
14
|
+
this software without specific prior written permission.
|
|
15
|
+
|
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
17
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
18
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
19
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
20
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
21
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
22
|
+
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
23
|
+
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
24
|
+
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
25
|
+
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
26
|
+
SUCH DAMAGE.
|
package/README.md
ADDED
package/index.d.ts
ADDED
package/index.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@devvit/server",
|
|
3
|
+
"version": "0.11.14-next-2025-05-05-765f3688c.0",
|
|
4
|
+
"license": "BSD-3-Clause",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://developers.reddit.com/"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "./index.js",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"clean": "rm -rf .turbo coverage dist",
|
|
14
|
+
"clobber": "yarn clean && rm -rf node_modules",
|
|
15
|
+
"dev": "tsc -w",
|
|
16
|
+
"lint": "redlint .",
|
|
17
|
+
"lint:fix": "yarn lint --fix",
|
|
18
|
+
"prepublishOnly": "publish-package-json",
|
|
19
|
+
"test": "yarn test:unit && yarn test:types && yarn lint",
|
|
20
|
+
"test:types": "tsc --noEmit",
|
|
21
|
+
"test:unit": "vitest run",
|
|
22
|
+
"test:unit-with-coverage": "vitest run --coverage"
|
|
23
|
+
},
|
|
24
|
+
"types": "./index.d.ts",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@devvit/protos": "0.11.14-next-2025-05-05-765f3688c.0",
|
|
27
|
+
"@devvit/public-api": "0.11.14-next-2025-05-05-765f3688c.0",
|
|
28
|
+
"@devvit/shared-types": "0.11.14-next-2025-05-05-765f3688c.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@devvit/repo-tools": "0.11.14-next-2025-05-05-765f3688c.0",
|
|
32
|
+
"@devvit/tsconfig": "0.11.14-next-2025-05-05-765f3688c.0",
|
|
33
|
+
"eslint": "9.11.1",
|
|
34
|
+
"typescript": "5.3.2",
|
|
35
|
+
"vitest": "1.6.1"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"directory": "dist"
|
|
39
|
+
},
|
|
40
|
+
"source": "./src/index.ts",
|
|
41
|
+
"gitHead": "f74f2c6405eac9d70b8ac67cdd13b24370bddb4a"
|
|
42
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Metadata } from '@devvit/protos';
|
|
2
|
+
import type { Context } from '@devvit/public-api';
|
|
3
|
+
/** Devvit server context for the lifetime of a request. */
|
|
4
|
+
export type RequestContext = Omit<Context, 'dimensions' | 'kvStore' | 'modLog' | 'ui' | 'uiEnvironment'>;
|
|
5
|
+
/** Designed to be compatible with IncomingHttpHeaders and any KV. */
|
|
6
|
+
type Headers = {
|
|
7
|
+
[header: string]: string | string[] | undefined;
|
|
8
|
+
};
|
|
9
|
+
/** Constructs a new RequestContext. */
|
|
10
|
+
export declare let RequestContext: (headers: Readonly<Headers>) => RequestContext;
|
|
11
|
+
/**
|
|
12
|
+
* Overwrite the context provider for test.
|
|
13
|
+
* @experimental
|
|
14
|
+
*/
|
|
15
|
+
export declare function setRequestContext(fn: typeof RequestContext): void;
|
|
16
|
+
/** @internal */
|
|
17
|
+
export declare function metaFromIncomingMessage(headers: Readonly<Headers>): Metadata;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=request-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-context.d.ts","sourceRoot":"","sources":["../src/request-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAKlD,2DAA2D;AAC3D,MAAM,MAAM,cAAc,GAAG,IAAI,CAC/B,OAAO,EACP,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,IAAI,GAAG,eAAe,CAC7D,CAAC;AAEF,qEAAqE;AACrE,KAAK,OAAO,GAAG;IAAE,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAA;CAAE,CAAC;AAEnE,uCAAuC;AACvC,eAAO,IAAI,cAAc,YAAa,SAAS,OAAO,CAAC,KAAG,cAMzD,CAAC;AAEF;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,OAAO,cAAc,GAAG,IAAI,CAEjE;AAED,gBAAgB;AAChB,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,QAAQ,CAM5E"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { makeAPIClients } from '@devvit/public-api/apis/makeAPIClients.js';
|
|
2
|
+
import { getContextFromMetadata } from '@devvit/public-api/devvit/internals/context.js';
|
|
3
|
+
import { Header, headerPrefix } from '@devvit/shared-types/Header.js';
|
|
4
|
+
/** Constructs a new RequestContext. */
|
|
5
|
+
export let RequestContext = (headers) => {
|
|
6
|
+
const meta = metaFromIncomingMessage(headers);
|
|
7
|
+
return Object.assign(makeAPIClients({ metadata: meta }), getContextFromMetadata(meta, meta[Header.Post]?.values[0]));
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Overwrite the context provider for test.
|
|
11
|
+
* @experimental
|
|
12
|
+
*/
|
|
13
|
+
export function setRequestContext(fn) {
|
|
14
|
+
RequestContext = fn;
|
|
15
|
+
}
|
|
16
|
+
/** @internal */
|
|
17
|
+
export function metaFromIncomingMessage(headers) {
|
|
18
|
+
const meta = {};
|
|
19
|
+
for (const [key, val] of Object.entries(headers))
|
|
20
|
+
if (key.startsWith(headerPrefix))
|
|
21
|
+
meta[key] = { values: typeof val === 'object' ? val : val == null ? [] : [val] };
|
|
22
|
+
return meta;
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-context.test.d.ts","sourceRoot":"","sources":["../src/request-context.test.ts"],"names":[],"mappings":""}
|
package/webbit-post.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type Context, type CustomPostType, type SubmitCustomPostOptions } from '@devvit/public-api';
|
|
2
|
+
/** Configuration parameters for creating a webbit post type. */
|
|
3
|
+
type WebbitPostParams = Omit<CustomPostType, 'render'> & Partial<Pick<CustomPostType, 'render'>> & {
|
|
4
|
+
/** The path to the HTML entrypoint for the frontend */
|
|
5
|
+
entry: string;
|
|
6
|
+
menu?: WebbitMenuParams;
|
|
7
|
+
/** If true, the webbit post will be rendered inline in the post body instead of a focus mode webview with a launch button. */
|
|
8
|
+
inline?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* The text for the button that launches the webbit app in focus mode
|
|
11
|
+
* @defaultValue 'Launch App'
|
|
12
|
+
*/
|
|
13
|
+
launchButtonText?: string;
|
|
14
|
+
};
|
|
15
|
+
/** Optional parameters for the automatically created subreddit menu item for creating posts. */
|
|
16
|
+
type WebbitMenuParams = Partial<Pick<SubmitCustomPostOptions, 'preview'>> & {
|
|
17
|
+
/**
|
|
18
|
+
* Whether the menu item is enabled
|
|
19
|
+
* @defaultValue true
|
|
20
|
+
*/
|
|
21
|
+
enable?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* The label for the menu item
|
|
24
|
+
* @defaultValue '[${app name}] New Post'
|
|
25
|
+
*/
|
|
26
|
+
label?: string;
|
|
27
|
+
/** The title of the post to be created. Can be a static string or a function that returns a string. */
|
|
28
|
+
postTitle?: string | ((context: Context) => Promise<string> | string);
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
*
|
|
32
|
+
* @param {WebbitPostParams }params Parameters for the webbit post type.
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* webbitEnablePost({
|
|
36
|
+
* name: 'Hello Webbit',
|
|
37
|
+
* description: 'Custom webbit post',
|
|
38
|
+
* height: 'tall',
|
|
39
|
+
* entry: 'page.html',
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare function webbitEnablePost(params: WebbitPostParams): void;
|
|
44
|
+
export {};
|
|
45
|
+
//# sourceMappingURL=webbit-post.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webbit-post.d.ts","sourceRoot":"","sources":["../src/webbit-post.tsx"],"names":[],"mappings":"AAKA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,cAAc,EAKnB,KAAK,uBAAuB,EAI7B,MAAM,oBAAoB,CAAC;AAM5B,gEAAgE;AAChE,KAAK,gBAAgB,GAAG,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,GACpD,OAAO,CAAC,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC,GAAG;IACxC,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,8HAA8H;IAC9H,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEJ,gGAAgG;AAChG,KAAK,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,uBAAuB,EAAE,SAAS,CAAC,CAAC,GAAG;IAC1E;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uGAAuG;IACvG,SAAS,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC;CACvE,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CA6B/D"}
|
package/webbit-post.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { EffectType } from '@devvit/protos';
|
|
2
|
+
import { WebbitServerDefinition, } from '@devvit/protos/types/devvit/actor/webbit/webbit.js';
|
|
3
|
+
import { Devvit, useChannel, useState, useWebView, } from '@devvit/public-api';
|
|
4
|
+
import { useEffectEmitter } from '@devvit/public-api/devvit/internals/blocks/handler/BlocksHandler.js';
|
|
5
|
+
import { extendDevvitPrototype } from '@devvit/public-api/devvit/internals/helpers/extendDevvitPrototype.js';
|
|
6
|
+
import { newWebbitServer } from './webbit-server.js';
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* @param {WebbitPostParams }params Parameters for the webbit post type.
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* webbitEnablePost({
|
|
13
|
+
* name: 'Hello Webbit',
|
|
14
|
+
* description: 'Custom webbit post',
|
|
15
|
+
* height: 'tall',
|
|
16
|
+
* entry: 'page.html',
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function webbitEnablePost(params) {
|
|
21
|
+
// add the webbit server to the Devvit prototype
|
|
22
|
+
const server = newWebbitServer();
|
|
23
|
+
extendDevvitPrototype('Request', server.Request);
|
|
24
|
+
// enable Devvit features
|
|
25
|
+
Devvit.configure({
|
|
26
|
+
realtime: true,
|
|
27
|
+
redditAPI: true,
|
|
28
|
+
});
|
|
29
|
+
Devvit.provide(WebbitServerDefinition);
|
|
30
|
+
let render = (context) => params.inline ? defaultInlineRender(context, params) : defaultRender(params);
|
|
31
|
+
if (params.render) {
|
|
32
|
+
render = params.render;
|
|
33
|
+
}
|
|
34
|
+
// setup the custom post type
|
|
35
|
+
Devvit.addCustomPostType({ ...params, render });
|
|
36
|
+
// setup a menu item to create a webbit post
|
|
37
|
+
if (params.menu?.enable === false) {
|
|
38
|
+
// If the menu is disabled, we don't add a menu item.
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
Devvit.addMenuItem(defaultMenuItem(params));
|
|
42
|
+
}
|
|
43
|
+
function defaultRender(params) {
|
|
44
|
+
const launchButtonText = params.launchButtonText ?? 'Launch App';
|
|
45
|
+
const [subscriptions, setSubscriptions] = useState([]);
|
|
46
|
+
const onMessage = messageHandler(setSubscriptions);
|
|
47
|
+
const { mount, postMessage } = useWebView({
|
|
48
|
+
// URL of your web view content
|
|
49
|
+
url: params.entry,
|
|
50
|
+
// Handle messages from web view
|
|
51
|
+
onMessage,
|
|
52
|
+
});
|
|
53
|
+
messageRelay(subscriptions, postMessage);
|
|
54
|
+
return (Devvit.createElement("vstack", { alignment: "center middle", height: "100%" },
|
|
55
|
+
Devvit.createElement("button", { onPress: mount }, launchButtonText),
|
|
56
|
+
";"));
|
|
57
|
+
}
|
|
58
|
+
function defaultInlineRender(context, params) {
|
|
59
|
+
const webViewId = 'webView';
|
|
60
|
+
const [subscriptions, setSubscriptions] = useState([]);
|
|
61
|
+
messageRelay(subscriptions, (msg) => {
|
|
62
|
+
context.ui.webView.postMessage(webViewId, msg);
|
|
63
|
+
});
|
|
64
|
+
const onMessage = messageHandler(setSubscriptions);
|
|
65
|
+
return (Devvit.createElement("webview", { id: webViewId, url: params.entry, width: "100%", height: "100%", onMessage: onMessage }));
|
|
66
|
+
}
|
|
67
|
+
function messageHandler(setSubscriptions) {
|
|
68
|
+
const emitter = useEffectEmitter();
|
|
69
|
+
return (message) => {
|
|
70
|
+
// if is object, check type
|
|
71
|
+
if (typeof message === 'object' &&
|
|
72
|
+
message !== null &&
|
|
73
|
+
'type' in message &&
|
|
74
|
+
message.type === 'effect') {
|
|
75
|
+
const effect = message.data;
|
|
76
|
+
const dedupeKey = message.key;
|
|
77
|
+
// Needs to be handled specially because its a dynamic subscription set.
|
|
78
|
+
if (effect.type === EffectType.EFFECT_REALTIME_SUB) {
|
|
79
|
+
setSubscriptions(effect.realtimeSubscriptions?.subscriptionIds ?? []);
|
|
80
|
+
}
|
|
81
|
+
emitter.emitEffect(dedupeKey, effect);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function messageRelay(subscriptions, sender) {
|
|
86
|
+
for (const sub of subscriptions) {
|
|
87
|
+
const channel = useChannel({
|
|
88
|
+
name: sub,
|
|
89
|
+
onMessage: (msg) => {
|
|
90
|
+
sender({
|
|
91
|
+
type: 'realtime',
|
|
92
|
+
channel: sub,
|
|
93
|
+
msg,
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
channel.subscribe();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function defaultMenuItem({ name, menu }) {
|
|
101
|
+
return {
|
|
102
|
+
forUserType: ['moderator'],
|
|
103
|
+
label: menu?.label || `[${name}] New Post`,
|
|
104
|
+
location: 'subreddit',
|
|
105
|
+
async onPress(_ev, ctx) {
|
|
106
|
+
const title = typeof menu?.postTitle === 'function'
|
|
107
|
+
? await menu?.postTitle(ctx)
|
|
108
|
+
: menu?.postTitle || name;
|
|
109
|
+
if (!ctx.subredditName)
|
|
110
|
+
throw Error('invalid subreddit name');
|
|
111
|
+
const post = await ctx.reddit.submitPost({
|
|
112
|
+
preview: menu?.preview || Devvit.createElement("text", null, "Loading\u2026"),
|
|
113
|
+
subredditName: ctx.subredditName,
|
|
114
|
+
title,
|
|
115
|
+
});
|
|
116
|
+
await ctx.ui.showToast({
|
|
117
|
+
text: `Created post "${title}" in ${ctx.subredditName}. Navigating to it now.`,
|
|
118
|
+
appearance: 'success',
|
|
119
|
+
});
|
|
120
|
+
ctx.ui.navigateTo(post);
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webbit-server.d.ts","sourceRoot":"","sources":["../src/webbit-server.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,YAAY,EAElB,MAAM,oDAAoD,CAAC;AAI5D,wBAAgB,YAAY,IAAI,IAAI,CAInC;AAED,wBAAgB,eAAe,IAAI,YAAY,CAsB9C"}
|
package/webbit-server.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { HttpMethod, WebbitServerDefinition, } from '@devvit/protos/types/devvit/actor/webbit/webbit.js';
|
|
2
|
+
import { Devvit } from '@devvit/public-api';
|
|
3
|
+
import { extendDevvitPrototype } from '@devvit/public-api/devvit/internals/helpers/extendDevvitPrototype.js';
|
|
4
|
+
export function webbitEnable() {
|
|
5
|
+
const server = newWebbitServer();
|
|
6
|
+
extendDevvitPrototype('Request', server.Request);
|
|
7
|
+
Devvit.provide(WebbitServerDefinition);
|
|
8
|
+
}
|
|
9
|
+
export function newWebbitServer() {
|
|
10
|
+
return {
|
|
11
|
+
async Request(req, meta) {
|
|
12
|
+
const port = process.env.WEBBIT_PORT || '3000';
|
|
13
|
+
// only set the body if the method is not GET or HEAD
|
|
14
|
+
const body = [HttpMethod.GET, HttpMethod.HEAD].includes(req.method) ? null : req.body;
|
|
15
|
+
const rsp = await fetch(new URL(req.path, `http://webbit.local:${port}`), {
|
|
16
|
+
body,
|
|
17
|
+
headers: [
|
|
18
|
+
...Object.entries(req.headers),
|
|
19
|
+
// devvit- headers alway have priority.
|
|
20
|
+
...Object.entries(meta ?? {}).map(([k, v]) => [k, v.values.join()]),
|
|
21
|
+
],
|
|
22
|
+
method: HttpMethod[req.method],
|
|
23
|
+
});
|
|
24
|
+
const headers = {};
|
|
25
|
+
rsp.headers.forEach((v, k) => (headers[k] = v));
|
|
26
|
+
return { body: new Uint8Array(await rsp.arrayBuffer()), headers, statusCode: rsp.status };
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|