@devvit/server 0.11.19-next-2025-07-02-19-55-03-bc5010226.0 → 0.11.19-next-2025-07-02-23-07-18-cc63be6e8.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/index.d.ts +0 -1
- package/index.d.ts.map +1 -1
- package/index.js +0 -1
- package/package.json +8 -8
- package/define-config.d.ts +0 -65
- package/define-config.d.ts.map +0 -1
- package/define-config.js +0 -271
- package/define-config.test.d.ts.map +0 -1
package/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export { context } from './context.js';
|
|
2
2
|
export { createServer } from './create-server.js';
|
|
3
|
-
export { defineConfig } from './define-config.js';
|
|
4
3
|
export { getServerPort } from './get-server-port.js';
|
|
5
4
|
export { RequestContext } from './request-context.js';
|
|
6
5
|
//# sourceMappingURL=index.d.ts.map
|
package/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC"}
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devvit/server",
|
|
3
|
-
"version": "0.11.19-next-2025-07-02-
|
|
3
|
+
"version": "0.11.19-next-2025-07-02-23-07-18-cc63be6e8.0",
|
|
4
4
|
"license": "BSD-3-Clause",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -23,14 +23,14 @@
|
|
|
23
23
|
},
|
|
24
24
|
"types": "./index.d.ts",
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@devvit/protos": "0.11.19-next-2025-07-02-
|
|
27
|
-
"@devvit/public-api": "0.11.19-next-2025-07-02-
|
|
28
|
-
"@devvit/shared": "0.11.19-next-2025-07-02-
|
|
29
|
-
"@devvit/shared-types": "0.11.19-next-2025-07-02-
|
|
26
|
+
"@devvit/protos": "0.11.19-next-2025-07-02-23-07-18-cc63be6e8.0",
|
|
27
|
+
"@devvit/public-api": "0.11.19-next-2025-07-02-23-07-18-cc63be6e8.0",
|
|
28
|
+
"@devvit/shared": "0.11.19-next-2025-07-02-23-07-18-cc63be6e8.0",
|
|
29
|
+
"@devvit/shared-types": "0.11.19-next-2025-07-02-23-07-18-cc63be6e8.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@devvit/repo-tools": "0.11.19-next-2025-07-02-
|
|
33
|
-
"@devvit/tsconfig": "0.11.19-next-2025-07-02-
|
|
32
|
+
"@devvit/repo-tools": "0.11.19-next-2025-07-02-23-07-18-cc63be6e8.0",
|
|
33
|
+
"@devvit/tsconfig": "0.11.19-next-2025-07-02-23-07-18-cc63be6e8.0",
|
|
34
34
|
"eslint": "9.11.1",
|
|
35
35
|
"typescript": "5.8.3",
|
|
36
36
|
"vitest": "1.6.1"
|
|
@@ -39,5 +39,5 @@
|
|
|
39
39
|
"directory": "dist"
|
|
40
40
|
},
|
|
41
41
|
"source": "./src/index.ts",
|
|
42
|
-
"gitHead": "
|
|
42
|
+
"gitHead": "3ad54dc774a5ec586700b0fb23112b3d606db3fb"
|
|
43
43
|
}
|
package/define-config.d.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { Devvit, type FormKey } from '@devvit/public-api';
|
|
2
|
-
import type { UiResponse } from '@devvit/shared/types/ui-response.js';
|
|
3
|
-
import type { AppConfig, AppFormsConfig, AppMenuActionConfig, AppPermissionConfig, AppPostCreateConfig, AppSchedulerConfig } from '@devvit/shared-types/schemas/config-file.v1.js';
|
|
4
|
-
/**
|
|
5
|
-
* A subset of AppConfig (see config-file.v1.json) available in V8-flavored
|
|
6
|
-
* LinkedBundles except for the deprecated Blocks callbacks. This config is used
|
|
7
|
-
* to generate classic compatible builds for clients via the Devvit CLI and
|
|
8
|
-
* configure the singleton at runtime for the compute bootstrap. The dynamic
|
|
9
|
-
* behavior is for old templates.
|
|
10
|
-
*
|
|
11
|
-
* @experimental
|
|
12
|
-
*/
|
|
13
|
-
export type DynamicAppConfig = {
|
|
14
|
-
permissions: AppPermissionConfig;
|
|
15
|
-
post?: DynamicAppPostConfig | undefined;
|
|
16
|
-
menuActions?: AppMenuActionConfig[];
|
|
17
|
-
scheduler?: AppSchedulerConfig | undefined;
|
|
18
|
-
forms?: AppFormsConfig;
|
|
19
|
-
};
|
|
20
|
-
/** @experimental */
|
|
21
|
-
export type DynamicAppPostConfig = {
|
|
22
|
-
client: {
|
|
23
|
-
dir: string;
|
|
24
|
-
entry: string;
|
|
25
|
-
/** @deprecated */
|
|
26
|
-
blocks?: {
|
|
27
|
-
/** Render post inline or use custom renderer. */
|
|
28
|
-
render?: 'inline' | {
|
|
29
|
-
mode: 'modal';
|
|
30
|
-
/**
|
|
31
|
-
* The text for the button that launches the app in a modal.
|
|
32
|
-
* @default 'Launch App'
|
|
33
|
-
*/
|
|
34
|
-
label?: (() => string) | undefined;
|
|
35
|
-
} | Devvit.CustomPostComponent | undefined;
|
|
36
|
-
};
|
|
37
|
-
};
|
|
38
|
-
create: DynamicAppPostCreateConfig;
|
|
39
|
-
};
|
|
40
|
-
/** @experimental */
|
|
41
|
-
export type DynamicAppPostCreateConfig = AppPostCreateConfig & {
|
|
42
|
-
/** @deprecated */
|
|
43
|
-
blocks?: {
|
|
44
|
-
/**
|
|
45
|
-
* App loading screen. Evaluated at post creation time. Call
|
|
46
|
-
* `Post.setCustomPostPreview()` to change after creation.
|
|
47
|
-
* @default 'Loading…'
|
|
48
|
-
*/
|
|
49
|
-
render?(): JSX.Element | undefined;
|
|
50
|
-
/**
|
|
51
|
-
* Dynamic post title.
|
|
52
|
-
* @default post.create.title
|
|
53
|
-
*/
|
|
54
|
-
title?(): string | Promise<string> | undefined;
|
|
55
|
-
} | undefined;
|
|
56
|
-
};
|
|
57
|
-
/** @internal [state] Map of devvit.json form keys to Devvit-singleton form keys. */
|
|
58
|
-
export declare const formKeyMap: {
|
|
59
|
-
[formKey: string]: FormKey;
|
|
60
|
-
};
|
|
61
|
-
/** @experimental */
|
|
62
|
-
export declare function defineConfig(config: Readonly<DynamicAppConfig | AppConfig>): typeof Devvit;
|
|
63
|
-
/** @internal */
|
|
64
|
-
export declare function validateUiResponse(uiResponse: UiResponse): void;
|
|
65
|
-
//# sourceMappingURL=define-config.d.ts.map
|
package/define-config.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"define-config.d.ts","sourceRoot":"","sources":["../src/define-config.tsx"],"names":[],"mappings":"AACA,OAAO,EAEL,MAAM,EACN,KAAK,OAAO,EAMb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qCAAqC,CAAC;AAEtE,OAAO,KAAK,EACV,SAAS,EACT,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EACnB,MAAM,gDAAgD,CAAC;AAIxD;;;;;;;;GAQG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,mBAAmB,CAAC;IACjC,IAAI,CAAC,EAAE,oBAAoB,GAAG,SAAS,CAAC;IACxC,WAAW,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACpC,SAAS,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IAC3C,KAAK,CAAC,EAAE,cAAc,CAAC;CACxB,CAAC;AAEF,oBAAoB;AACpB,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE;QACN,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,kBAAkB;QAClB,MAAM,CAAC,EAAE;YACP,iDAAiD;YACjD,MAAM,CAAC,EACH,QAAQ,GACR;gBACE,IAAI,EAAE,OAAO,CAAC;gBACd;;;mBAGG;gBACH,KAAK,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;aACpC,GACD,MAAM,CAAC,mBAAmB,GAC1B,SAAS,CAAC;SACf,CAAC;KACH,CAAC;IACF,MAAM,EAAE,0BAA0B,CAAC;CACpC,CAAC;AAEF,oBAAoB;AACpB,MAAM,MAAM,0BAA0B,GAAG,mBAAmB,GAAG;IAC7D,kBAAkB;IAClB,MAAM,CAAC,EACH;QACE;;;;WAIG;QACH,MAAM,CAAC,IAAI,GAAG,CAAC,OAAO,GAAG,SAAS,CAAC;QACnC;;;WAGG;QACH,KAAK,CAAC,IAAI,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;KAChD,GACD,SAAS,CAAC;CACf,CAAC;AAEF,oFAAoF;AACpF,eAAO,MAAM,UAAU,EAAE;IAAE,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAA;CAAO,CAAC;AAE7D,oBAAoB;AACpB,wBAAgB,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,gBAAgB,GAAG,SAAS,CAAC,GAAG,OAAO,MAAM,CAO1F;AAyGD,gBAAgB;AAChB,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CA4D/D"}
|
package/define-config.js
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
import { Devvit, useWebView, } from '@devvit/public-api';
|
|
2
|
-
import { getServerPort } from './get-server-port.js';
|
|
3
|
-
/** @internal [state] Map of devvit.json form keys to Devvit-singleton form keys. */
|
|
4
|
-
export const formKeyMap = {};
|
|
5
|
-
/** @experimental */
|
|
6
|
-
export function defineConfig(config) {
|
|
7
|
-
configurePermissions(config.permissions);
|
|
8
|
-
if (config.post)
|
|
9
|
-
configurePost(config.post);
|
|
10
|
-
if (config.menuActions)
|
|
11
|
-
configureMenuActions(config.menuActions);
|
|
12
|
-
if (config.scheduler)
|
|
13
|
-
configureScheduler(config.scheduler);
|
|
14
|
-
if (config.forms)
|
|
15
|
-
configureForms(config.forms);
|
|
16
|
-
return Devvit;
|
|
17
|
-
}
|
|
18
|
-
function configurePermissions(permissions) {
|
|
19
|
-
// to-do: remove. This is a relic of LinkedBundle generation.
|
|
20
|
-
Devvit.configure({
|
|
21
|
-
http: {
|
|
22
|
-
enabled: permissions.http.enable,
|
|
23
|
-
domains: permissions.http.domains,
|
|
24
|
-
},
|
|
25
|
-
media: permissions.media,
|
|
26
|
-
// to-do: payments permissions.
|
|
27
|
-
realtime: permissions.realtime,
|
|
28
|
-
redditAPI: permissions.reddit.enable,
|
|
29
|
-
modLog: permissions.reddit.scope === 'moderator',
|
|
30
|
-
// to-do: configure userActions with API granularity.
|
|
31
|
-
userActions: !!permissions.reddit.asUser.length,
|
|
32
|
-
redis: permissions.redis,
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
function configurePost(post) {
|
|
36
|
-
let render;
|
|
37
|
-
// eslint-disable-next-line security/detect-non-literal-regexp
|
|
38
|
-
const path = post.client.entry.replace(new RegExp(`^${post.client.dir}/?`), '');
|
|
39
|
-
if (post.client.blocks?.render === 'inline')
|
|
40
|
-
render = renderInline.bind(undefined, path);
|
|
41
|
-
else if (typeof post.client.blocks?.render === 'function')
|
|
42
|
-
render = post.client.blocks.render;
|
|
43
|
-
else
|
|
44
|
-
render = renderModal.bind(undefined, path, post.client.blocks?.render?.label);
|
|
45
|
-
Devvit.addCustomPostType({
|
|
46
|
-
name: post.create.title,
|
|
47
|
-
render,
|
|
48
|
-
height: post.create.height,
|
|
49
|
-
});
|
|
50
|
-
if (post.create.menu.enable) {
|
|
51
|
-
Devvit.addMenuItem({
|
|
52
|
-
forUserType: post.create.menu.scope === 'moderator' ? 'moderator' : 'loggedOut',
|
|
53
|
-
label: post.create.menu.label,
|
|
54
|
-
location: 'subreddit',
|
|
55
|
-
async onPress(_ev, ctx) {
|
|
56
|
-
const newPost = await createPost(ctx, post.create);
|
|
57
|
-
ctx.ui.showToast({
|
|
58
|
-
text: `Created post "${newPost.title}" in ${ctx.subredditName}. Navigating to it now.`,
|
|
59
|
-
appearance: 'success',
|
|
60
|
-
});
|
|
61
|
-
ctx.ui.navigateTo(newPost);
|
|
62
|
-
},
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
if (post.create.onInstall)
|
|
66
|
-
Devvit.addTrigger({
|
|
67
|
-
event: 'AppInstall',
|
|
68
|
-
async onEvent(_ev, ctx) {
|
|
69
|
-
await createPost(ctx, post.create);
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
function configureMenuActions(menuActions) {
|
|
74
|
-
for (const action of menuActions) {
|
|
75
|
-
const menuItem = {
|
|
76
|
-
label: action.label,
|
|
77
|
-
location: action.location,
|
|
78
|
-
onPress: async (ev, ctx) => {
|
|
79
|
-
const responseJson = await callWebbitEndpoint(action.endpoint, ev, ctx.debug.metadata);
|
|
80
|
-
const uiResponse = responseJson;
|
|
81
|
-
validateUiResponse(uiResponse);
|
|
82
|
-
await handleUiResponse(ctx, uiResponse);
|
|
83
|
-
},
|
|
84
|
-
};
|
|
85
|
-
// "user" type is blank in Devvit classic. So if it's present in
|
|
86
|
-
// devvit.json, we add an extra menu item without forUserType set.
|
|
87
|
-
// "moderator" maps across as expected.
|
|
88
|
-
// "loggedOut" type (Devvit classic) we no longer support
|
|
89
|
-
if (action.forUserType === 'moderator')
|
|
90
|
-
menuItem.forUserType = 'moderator';
|
|
91
|
-
if (action.postFilter === 'currentApp')
|
|
92
|
-
menuItem.postFilter = action.postFilter;
|
|
93
|
-
if (action.description)
|
|
94
|
-
menuItem.description = action.description;
|
|
95
|
-
Devvit.addMenuItem(menuItem);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
function configureForms(forms) {
|
|
99
|
-
for (const [name, endpoint] of Object.entries(forms)) {
|
|
100
|
-
formKeyMap[name] = Devvit.createForm({ fields: [] }, (ev, ctx) => handleFormResponse(endpoint, ev, ctx));
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
async function handleFormResponse(endpoint, event, ctx) {
|
|
104
|
-
const responseJson = await callWebbitEndpoint(endpoint, event.values, ctx.debug.metadata);
|
|
105
|
-
const uiResponse = responseJson;
|
|
106
|
-
validateUiResponse(uiResponse);
|
|
107
|
-
await handleUiResponse(ctx, uiResponse);
|
|
108
|
-
}
|
|
109
|
-
// TODO: expand this to fully validate the UiResponse format, including Form definitions,
|
|
110
|
-
// and convert signature to validateUiResponse(uiResponse: JSONValue): uiResponse is UiResponse
|
|
111
|
-
/** @internal */
|
|
112
|
-
export function validateUiResponse(uiResponse) {
|
|
113
|
-
// Validations:
|
|
114
|
-
// (1) The only valid fields on uiResponse are showToast, navigateTo, and showForm.
|
|
115
|
-
const validKeys = ['showToast', 'navigateTo', 'showForm'];
|
|
116
|
-
const invalidKeys = Object.keys(uiResponse).filter((key) => !validKeys.includes(key));
|
|
117
|
-
if (invalidKeys.length > 0) {
|
|
118
|
-
throw new Error(`Invalid fields found in UiResponse: ${invalidKeys.join(', ')}. Valid fields are: ${validKeys.join(', ')}`);
|
|
119
|
-
}
|
|
120
|
-
// (2) showForm must have a name that exists in the formKeyMap.
|
|
121
|
-
if (uiResponse.showForm) {
|
|
122
|
-
if (!uiResponse.showForm.name) {
|
|
123
|
-
throw new Error('showForm must have a name');
|
|
124
|
-
}
|
|
125
|
-
if (!formKeyMap[uiResponse.showForm.name]) {
|
|
126
|
-
throw new Error(`Form with name ${uiResponse.showForm.name} not found in devvit.json. Consider adding '"forms"."${uiResponse.showForm.name}"="/internal/your/endpoint"'.`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
// (3) showToast must be a string or an object with only fields [text, appearance]
|
|
130
|
-
// and text is a mandatory string.
|
|
131
|
-
if (uiResponse.showToast !== undefined) {
|
|
132
|
-
if (typeof uiResponse.showToast === 'string') {
|
|
133
|
-
// Valid case: showToast is a string
|
|
134
|
-
}
|
|
135
|
-
else if (typeof uiResponse.showToast === 'object' && uiResponse.showToast !== null) {
|
|
136
|
-
// Check if it's an object with valid fields
|
|
137
|
-
const toastKeys = Object.keys(uiResponse.showToast);
|
|
138
|
-
const validToastKeys = ['text', 'appearance'];
|
|
139
|
-
const invalidToastKeys = toastKeys.filter((key) => !validToastKeys.includes(key));
|
|
140
|
-
if (invalidToastKeys.length > 0) {
|
|
141
|
-
throw new Error(`Invalid fields found in showToast: ${invalidToastKeys.join(', ')}. Valid fields are: ${validToastKeys.join(', ')}`);
|
|
142
|
-
}
|
|
143
|
-
if (typeof uiResponse.showToast.text !== 'string') {
|
|
144
|
-
throw new Error('showToast.text is required and must be a string');
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
throw new Error('showToast must be a string or an object with text and optional appearance fields');
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
// (4) navigateTo must be a valid URL or a Post object. (this is validated client-side, so
|
|
152
|
-
// we don't need to validate it here).
|
|
153
|
-
// (5) navigateTo and showForm are mutually exclusive.
|
|
154
|
-
if (uiResponse.navigateTo && uiResponse.showForm) {
|
|
155
|
-
throw new Error('navigateTo and showForm cannot be used together in UiResponse');
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Handle a UiResponse from a Webbit handler (menu action or form handler).
|
|
160
|
-
* This is used to create client-side UI effects in Reddit clients as responses
|
|
161
|
-
* to user actions in cases where the Devvit app is not in the direct code path.
|
|
162
|
-
*
|
|
163
|
-
* If multiple effects are present in the UiResponse, they will all be applied.
|
|
164
|
-
*/
|
|
165
|
-
async function handleUiResponse(ctx, uiResponse) {
|
|
166
|
-
if (uiResponse.showToast) {
|
|
167
|
-
ctx.ui.showToast(uiResponse.showToast);
|
|
168
|
-
}
|
|
169
|
-
if (uiResponse.navigateTo) {
|
|
170
|
-
ctx.ui.navigateTo(uiResponse.navigateTo);
|
|
171
|
-
}
|
|
172
|
-
if (uiResponse.showForm) {
|
|
173
|
-
ctx.ui.showFormInternal(formKeyMap[uiResponse.showForm.name], uiResponse.showForm.data, uiResponse.showForm.form);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
async function callWebbitEndpoint(endpoint, body, metadata) {
|
|
177
|
-
const url = new URL(endpoint, `http://webbit.local:${getServerPort()}/`);
|
|
178
|
-
const headers = {};
|
|
179
|
-
Object.entries(metadata).forEach(([key, metadata]) => {
|
|
180
|
-
headers[key] = metadata.values[0];
|
|
181
|
-
});
|
|
182
|
-
headers['Content-Type'] = 'application/json';
|
|
183
|
-
headers['Accept'] = 'application/json';
|
|
184
|
-
const response = await fetch(url, {
|
|
185
|
-
method: 'POST',
|
|
186
|
-
body: JSON.stringify(body),
|
|
187
|
-
headers,
|
|
188
|
-
});
|
|
189
|
-
if (!response.ok) {
|
|
190
|
-
const body = await response.text();
|
|
191
|
-
let errorMessage = `Failed to POST ${endpoint}: ${response.statusText}, `;
|
|
192
|
-
if (response.status === 404) {
|
|
193
|
-
errorMessage += `ensure that you're handling this endpoint in your server code.`;
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
errorMessage += `body: ${body.substring(0, 100)}`;
|
|
197
|
-
}
|
|
198
|
-
throw new Error(errorMessage);
|
|
199
|
-
}
|
|
200
|
-
const contentType = response.headers.get('Content-Type');
|
|
201
|
-
if (!contentType || !contentType.includes('application/json')) {
|
|
202
|
-
throw new Error(`Failed to POST ${endpoint}: expected response type 'application/json', received '${contentType}'`);
|
|
203
|
-
}
|
|
204
|
-
try {
|
|
205
|
-
return await response.json();
|
|
206
|
-
}
|
|
207
|
-
catch (error) {
|
|
208
|
-
throw new Error(`Failed to POST ${endpoint}: ${error}`);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
function configureScheduler(schedulerConfig) {
|
|
212
|
-
const cronTasks = {};
|
|
213
|
-
for (const [name, task] of Object.entries(schedulerConfig.tasks)) {
|
|
214
|
-
Devvit.addSchedulerJob({
|
|
215
|
-
name: name,
|
|
216
|
-
onRun: async (event, context) => {
|
|
217
|
-
await callWebbitEndpoint(task.endpoint, {
|
|
218
|
-
name: event.name,
|
|
219
|
-
data: event.data ?? null, // JSONObject wants null not undefined
|
|
220
|
-
}, context.debug.metadata);
|
|
221
|
-
},
|
|
222
|
-
});
|
|
223
|
-
// Tasks with cron specified require a bit more work further down
|
|
224
|
-
if (task.cron) {
|
|
225
|
-
cronTasks[name] = task;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
// If provided, schedule any cron tasks the user asked for on install/upgrade, being careful to
|
|
229
|
-
// un-schedule any previously scheduled instances of these tasks
|
|
230
|
-
if (Object.keys(cronTasks).length > 0) {
|
|
231
|
-
Devvit.addTrigger({
|
|
232
|
-
events: ['AppInstall', 'AppUpgrade'],
|
|
233
|
-
onEvent: async (_event, context) => {
|
|
234
|
-
// Get all jobs
|
|
235
|
-
const existingJobs = await context.scheduler.listJobs();
|
|
236
|
-
// Filter down to just cron jobs
|
|
237
|
-
const jobsToCancel = existingJobs.filter((job) => {
|
|
238
|
-
// Only cancel cron jobs
|
|
239
|
-
return 'cron' in job;
|
|
240
|
-
});
|
|
241
|
-
// Cancel everything we need to
|
|
242
|
-
await Promise.all(jobsToCancel.map((job) => context.scheduler.cancelJob(job.id)));
|
|
243
|
-
// Schedule all the cron tasks we were given in the config
|
|
244
|
-
await Promise.all(Object.entries(cronTasks).map(async ([name, task]) => {
|
|
245
|
-
await context.scheduler.runJob({
|
|
246
|
-
name: name,
|
|
247
|
-
cron: task.cron,
|
|
248
|
-
});
|
|
249
|
-
}));
|
|
250
|
-
},
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
async function createPost(ctx, create) {
|
|
255
|
-
if (!ctx.subredditName)
|
|
256
|
-
throw Error('no sub name');
|
|
257
|
-
return await ctx.reddit.submitPost({
|
|
258
|
-
preview: create.blocks?.render?.() ?? Devvit.createElement("text", null, "Loading\u2026"),
|
|
259
|
-
subredditName: ctx.subredditName,
|
|
260
|
-
title: (await create.blocks?.title?.()) ?? create.title,
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
function renderInline(path, _ctx) {
|
|
264
|
-
return Devvit.createElement("webview", { url: path, width: "100%", height: "100%" });
|
|
265
|
-
}
|
|
266
|
-
function renderModal(path, label, _ctx) {
|
|
267
|
-
const webView = useWebView({ url: path, onMessage() { } });
|
|
268
|
-
return (Devvit.createElement("vstack", { alignment: "center middle", height: "100%" },
|
|
269
|
-
Devvit.createElement("button", { onPress: webView.mount }, label?.() ?? 'Launch App'),
|
|
270
|
-
";"));
|
|
271
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"define-config.test.d.ts","sourceRoot":"","sources":["../src/define-config.test.ts"],"names":[],"mappings":""}
|