@appsemble/lang-sdk 0.40.8 → 0.40.10
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/schemas/AppDefinition.js +25 -1
- package/schemas/actions/EmailActionDefinition.js +1 -1
- package/types/core.d.ts +12 -2
- package/validation.js +41 -23
package/package.json
CHANGED
package/schemas/AppDefinition.js
CHANGED
|
@@ -27,7 +27,27 @@ This will be displayed on the app store.
|
|
|
27
27
|
description: 'Properties related to the layout of the app.',
|
|
28
28
|
},
|
|
29
29
|
notifications: {
|
|
30
|
-
|
|
30
|
+
oneOf: [
|
|
31
|
+
{ enum: ['login', 'opt-in', 'startup'] },
|
|
32
|
+
{
|
|
33
|
+
type: 'object',
|
|
34
|
+
required: ['roles'],
|
|
35
|
+
additionalProperties: false,
|
|
36
|
+
properties: {
|
|
37
|
+
default: {
|
|
38
|
+
enum: ['login', 'none', 'opt-in', 'startup'],
|
|
39
|
+
description: `The strategy for roles that aren’t listed in \`roles\`, and for users without
|
|
40
|
+
a role. Defaults to \`none\`.`,
|
|
41
|
+
},
|
|
42
|
+
roles: {
|
|
43
|
+
type: 'object',
|
|
44
|
+
minProperties: 1,
|
|
45
|
+
description: 'The strategy per role.',
|
|
46
|
+
additionalProperties: { enum: ['login', 'none', 'opt-in'] },
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
],
|
|
31
51
|
description: `The strategy to use for apps to subscribe to push notifications.
|
|
32
52
|
|
|
33
53
|
If specified, push notifications can be sent to subscribed users via the _Notifications_ tab in the
|
|
@@ -37,6 +57,10 @@ to \`startup\` will cause Appsemble to immediately request for the permission up
|
|
|
37
57
|
If this is set to \`login\`, the app will request permissions for push notification once the user
|
|
38
58
|
logs in.
|
|
39
59
|
|
|
60
|
+
To use a different strategy per role, set \`roles\` to a map of role names to \`login\`, \`opt-in\`, or
|
|
61
|
+
\`none\`. Roles that aren’t listed use \`default\`. A member with several roles gets the most eager
|
|
62
|
+
strategy of those roles. Only roles assigned to the member count, not roles they inherit.
|
|
63
|
+
|
|
40
64
|
> **Note**: Setting \`notifications\` to \`startup\` is not recommended, due to its invasive nature.
|
|
41
65
|
`,
|
|
42
66
|
},
|
|
@@ -42,7 +42,7 @@ If \`to\`, \`cc\`, and \`bcc\` all end up being empty, no emails will be sent bu
|
|
|
42
42
|
|
|
43
43
|
The remapper must resolve to an object containing the following properties:
|
|
44
44
|
|
|
45
|
-
- \`target\`: The asset ID or link to download contents from to add as an attachment. This is mutually exclusive with \`content\`.
|
|
45
|
+
- \`target\`: The asset ID or link to download contents from to add as an attachment. This is mutually exclusive with \`content\`. The files downloaded from links may total at most 25 MiB per email.
|
|
46
46
|
- \`content\`: The raw content to include as the file content. This is mutually exclusive with \`target\`.
|
|
47
47
|
- \`filename\`: The filename to include the attachment as.
|
|
48
48
|
- \`accept\` If the target is a URL, this will be set as the HTTP \`Accept\` header when downloading the file.
|
package/types/core.d.ts
CHANGED
|
@@ -184,9 +184,19 @@ export interface AppDefinition {
|
|
|
184
184
|
/**
|
|
185
185
|
* The strategy to use for apps to subscribe to push notifications.
|
|
186
186
|
*
|
|
187
|
-
* If this is omitted, push
|
|
187
|
+
* Either one strategy for all app members, or a strategy per role. If this is omitted, push
|
|
188
|
+
* notifications can not be sent.
|
|
188
189
|
*/
|
|
189
|
-
notifications?: 'login' | 'opt-in' | 'startup'
|
|
190
|
+
notifications?: 'login' | 'opt-in' | 'startup' | {
|
|
191
|
+
/**
|
|
192
|
+
* The strategy for roles that aren't listed in `roles`, and for users without a role.
|
|
193
|
+
*/
|
|
194
|
+
default?: 'login' | 'none' | 'opt-in' | 'startup';
|
|
195
|
+
/**
|
|
196
|
+
* The strategy per role.
|
|
197
|
+
*/
|
|
198
|
+
roles: Record<AppRole, 'login' | 'none' | 'opt-in'>;
|
|
199
|
+
};
|
|
190
200
|
/**
|
|
191
201
|
* The pages of the app.
|
|
192
202
|
*/
|
package/validation.js
CHANGED
|
@@ -97,7 +97,7 @@ function validateUniquePageNames(definition, report) {
|
|
|
97
97
|
pageNames.set(normalizedPageName, [pagePath]);
|
|
98
98
|
}
|
|
99
99
|
if (page.type === 'container') {
|
|
100
|
-
checkPages(page.pages, pagePath);
|
|
100
|
+
checkPages(page.pages ?? [], pagePath);
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
}
|
|
@@ -122,7 +122,7 @@ function iterPageParents(pages, onParent, prefix = ['pages']) {
|
|
|
122
122
|
onParent(entry, Array.isArray(page.parent) ? [...pagePath, 'parent', entryIndex] : [...pagePath, 'parent'], page);
|
|
123
123
|
}
|
|
124
124
|
if (page.type === 'container') {
|
|
125
|
-
iterPageParents(page.pages, onParent, [...pagePath, 'pages']);
|
|
125
|
+
iterPageParents(page.pages ?? [], onParent, [...pagePath, 'pages']);
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
}
|
|
@@ -703,7 +703,7 @@ function validateGridLayout(definition, report) {
|
|
|
703
703
|
// Flow page
|
|
704
704
|
if (page.type === 'flow') {
|
|
705
705
|
const flowPage = page;
|
|
706
|
-
const steps = flowPage.steps.map((step, stepId) => [
|
|
706
|
+
const steps = (flowPage.steps ?? []).map((step, stepId) => [
|
|
707
707
|
step,
|
|
708
708
|
[...path, 'steps', stepId],
|
|
709
709
|
]);
|
|
@@ -1122,8 +1122,9 @@ function validateSecurity(definition, report) {
|
|
|
1122
1122
|
checkRoleExists(role, [...path, 'roles', index], ['$guest', ...predefinedRoles]);
|
|
1123
1123
|
}
|
|
1124
1124
|
};
|
|
1125
|
+
const notificationRoles = notifications && typeof notifications === 'object' ? notifications.roles : undefined;
|
|
1125
1126
|
if (!security) {
|
|
1126
|
-
if (notifications === 'login') {
|
|
1127
|
+
if (notifications === 'login' || notificationRoles) {
|
|
1127
1128
|
report(notifications, 'only works if security is defined', ['notifications']);
|
|
1128
1129
|
}
|
|
1129
1130
|
return;
|
|
@@ -1224,6 +1225,11 @@ function validateSecurity(definition, report) {
|
|
|
1224
1225
|
}
|
|
1225
1226
|
});
|
|
1226
1227
|
}
|
|
1228
|
+
if (notificationRoles) {
|
|
1229
|
+
for (const role of Object.keys(notificationRoles)) {
|
|
1230
|
+
checkRoleExists(role, ['notifications', 'roles', role]);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1227
1233
|
const { hideGroupDropdown } = definition.layout ?? {};
|
|
1228
1234
|
if (Array.isArray(hideGroupDropdown)) {
|
|
1229
1235
|
for (const [index, role] of hideGroupDropdown.entries()) {
|
|
@@ -1819,6 +1825,9 @@ export async function validateAppDefinition(definition, getBlockVersions, contro
|
|
|
1819
1825
|
const report = (instance, message, path) => {
|
|
1820
1826
|
result.errors.push(new ValidationError(message, instance, undefined, path));
|
|
1821
1827
|
};
|
|
1828
|
+
const reportUnexpectedError = (error) => {
|
|
1829
|
+
report(null, `Unexpected error: ${error instanceof Error ? error.message : error}`, []);
|
|
1830
|
+
};
|
|
1822
1831
|
try {
|
|
1823
1832
|
// IMPORTANT: Create a deep copy to prevent validation logic from mutating the original definition.
|
|
1824
1833
|
// The imperative/procedural-style algorithms below (e.g., in validateActions) build up arrays
|
|
@@ -1834,27 +1843,36 @@ export async function validateAppDefinition(definition, getBlockVersions, contro
|
|
|
1834
1843
|
}
|
|
1835
1844
|
blockVersionMap.get(version.name).set(version.version, version);
|
|
1836
1845
|
}
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1846
|
+
// Run each check on its own, so a check that fails does not keep the others from reporting.
|
|
1847
|
+
const check = (validate) => {
|
|
1848
|
+
try {
|
|
1849
|
+
validate();
|
|
1850
|
+
}
|
|
1851
|
+
catch (error) {
|
|
1852
|
+
reportUnexpectedError(error);
|
|
1853
|
+
}
|
|
1854
|
+
};
|
|
1855
|
+
check(() => validateController(clonedDefinition, controllerImplementations, report));
|
|
1856
|
+
check(() => validateCronJobs(clonedDefinition, report));
|
|
1857
|
+
check(() => validateDefaultPage(clonedDefinition, report));
|
|
1858
|
+
check(() => validateHooks(clonedDefinition, report));
|
|
1859
|
+
check(() => validateIcons(clonedDefinition, report));
|
|
1860
|
+
check(() => validateIconReferences(clonedDefinition, blockVersionMap, report));
|
|
1861
|
+
check(() => validateLanguage(clonedDefinition, report));
|
|
1862
|
+
check(() => validateResourceReferences(clonedDefinition, report));
|
|
1863
|
+
check(() => validateMembersSchema(clonedDefinition, report));
|
|
1864
|
+
check(() => validatePageParents(clonedDefinition, report));
|
|
1865
|
+
check(() => validatePhoneNumberDefinition(clonedDefinition, report));
|
|
1866
|
+
check(() => validateResourceSchemas(clonedDefinition, report));
|
|
1867
|
+
check(() => validateSecurity(clonedDefinition, report));
|
|
1868
|
+
check(() => validateBlocks(clonedDefinition, blockVersionMap, report));
|
|
1869
|
+
check(() => validateActions(clonedDefinition, report));
|
|
1870
|
+
check(() => validateEvents(clonedDefinition, blockVersionMap, report));
|
|
1871
|
+
check(() => validateUniquePageNames(clonedDefinition, report));
|
|
1872
|
+
check(() => validateGridLayout(clonedDefinition, report));
|
|
1855
1873
|
}
|
|
1856
1874
|
catch (error) {
|
|
1857
|
-
|
|
1875
|
+
reportUnexpectedError(error);
|
|
1858
1876
|
}
|
|
1859
1877
|
return result;
|
|
1860
1878
|
}
|