@guinetik/primitives-ts 0.13.0 → 0.14.1
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/dist/email.types.d.ts +21 -2
- package/dist/email.types.d.ts.map +1 -1
- package/dist/geo.types.d.ts +19 -0
- package/dist/geo.types.d.ts.map +1 -0
- package/dist/geo.types.js +2 -0
- package/dist/home-dashboard.types.d.ts +47 -3
- package/dist/home-dashboard.types.d.ts.map +1 -1
- package/dist/home-dashboard.types.js +183 -7
- package/dist/home-dashboard.types.spec.js +145 -18
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/widget-services.types.d.ts +149 -0
- package/dist/widget-services.types.d.ts.map +1 -0
- package/dist/widget-services.types.js +97 -0
- package/dist/widget-services.types.spec.d.ts +2 -0
- package/dist/widget-services.types.spec.d.ts.map +1 -0
- package/dist/widget-services.types.spec.js +44 -0
- package/package.json +1 -1
package/dist/email.types.d.ts
CHANGED
|
@@ -75,20 +75,30 @@ export interface EmailVerifyResult {
|
|
|
75
75
|
ok: boolean;
|
|
76
76
|
error?: string;
|
|
77
77
|
}
|
|
78
|
+
export type ApiKeyScope = 'email:send' | 'geo';
|
|
78
79
|
/** API key metadata — never contains the full key. */
|
|
79
|
-
export interface
|
|
80
|
+
export interface ApiKey {
|
|
80
81
|
id: string;
|
|
81
82
|
name: string;
|
|
82
83
|
/** Free-form attribution label (e.g. agents@guinetik.com). Not an account link. */
|
|
83
84
|
owner?: string;
|
|
85
|
+
scopes: ApiKeyScope[];
|
|
84
86
|
prefix: string;
|
|
85
87
|
createdAt: string;
|
|
86
88
|
lastUsedAt?: string;
|
|
87
89
|
}
|
|
88
90
|
/** Returned once, at creation time only. */
|
|
89
|
-
export interface
|
|
91
|
+
export interface ApiKeyCreated extends ApiKey {
|
|
90
92
|
key: string;
|
|
91
93
|
}
|
|
94
|
+
/** @deprecated Use {@link ApiKey}. */
|
|
95
|
+
export type EmailApiKey = Omit<ApiKey, 'scopes'> & {
|
|
96
|
+
scopes?: ApiKeyScope[];
|
|
97
|
+
};
|
|
98
|
+
/** @deprecated Use {@link ApiKeyCreated}. */
|
|
99
|
+
export type EmailApiKeyCreated = EmailApiKey & {
|
|
100
|
+
key: string;
|
|
101
|
+
};
|
|
92
102
|
/** email-providers doc. Secret fields stored as enc:v1:... strings. */
|
|
93
103
|
export interface EmailProviderDocument extends FirestoreDocument {
|
|
94
104
|
type: EmailProviderType;
|
|
@@ -108,4 +118,13 @@ export interface EmailApiKeyDocument extends FirestoreDocument {
|
|
|
108
118
|
prefix: string;
|
|
109
119
|
lastUsedAt?: Date;
|
|
110
120
|
}
|
|
121
|
+
/** api-keys doc (shared collection, replaces email-api-keys). */
|
|
122
|
+
export interface ApiKeyDocument extends FirestoreDocument {
|
|
123
|
+
name: string;
|
|
124
|
+
owner?: string;
|
|
125
|
+
scopes: ApiKeyScope[];
|
|
126
|
+
keyHash: string;
|
|
127
|
+
prefix: string;
|
|
128
|
+
lastUsedAt?: Date;
|
|
129
|
+
}
|
|
111
130
|
//# sourceMappingURL=email.types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"email.types.d.ts","sourceRoot":"","sources":["../src/email.types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,0CAA0C;AAC1C,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,YAAY,GAAG,MAAM,CAAC;AAEjE,8CAA8C;AAC9C,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,4CAA4C;AAC5C,MAAM,WAAW,cAAe,SAAQ,yBAAyB;IAC/D,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,oDAAoD;AACpD,MAAM,WAAW,kBAAmB,SAAQ,yBAAyB;IACnE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,0CAA0C;AAC1C,MAAM,WAAW,YAAa,SAAQ,yBAAyB;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,qBAAqB,GAAG,cAAc,GAAG,kBAAkB,GAAG,YAAY,CAAC;AAEvF,uEAAuE;AACvE,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,iBAAiB,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,qBAAqB,CAAC;CACjC;AAED,gEAAgE;AAChE,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,8EAA8E;AAC9E,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,iBAAiB,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,2BAA2B,CAAC;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,4DAA4D;AAC5D,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,mCAAmC;AACnC,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,6CAA6C;AAC7C,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,sDAAsD;AACtD,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"email.types.d.ts","sourceRoot":"","sources":["../src/email.types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** A resolved place from the geo autocomplete service. */
|
|
2
|
+
export interface GeoLocation {
|
|
3
|
+
/** Display name as picked, e.g. "São José da Coroa Grande". */
|
|
4
|
+
name: string;
|
|
5
|
+
/** Secondary line, e.g. "Pernambuco, Brazil". */
|
|
6
|
+
region?: string;
|
|
7
|
+
lat: number;
|
|
8
|
+
lon: number;
|
|
9
|
+
/** IANA zone id derived server-side, e.g. "America/Recife". */
|
|
10
|
+
timeZone: string;
|
|
11
|
+
/** Google place id when resolved through Places. */
|
|
12
|
+
placeId?: string;
|
|
13
|
+
}
|
|
14
|
+
/** One autocomplete prediction; resolve via /geo/place/:placeId. */
|
|
15
|
+
export interface GeoSuggestion {
|
|
16
|
+
placeId: string;
|
|
17
|
+
description: string;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=geo.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geo.types.d.ts","sourceRoot":"","sources":["../src/geo.types.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,MAAM,WAAW,WAAW;IAC1B,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,oEAAoE;AACpE,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB"}
|
|
@@ -1,19 +1,61 @@
|
|
|
1
|
-
|
|
1
|
+
import type { GeoLocation } from "./geo.types";
|
|
2
|
+
export type HomeWidgetType = "launchpad" | "clock" | "webpage" | "analytics" | "utility" | "news" | "weather" | "currency";
|
|
2
3
|
export type HomeWidgetColSpan = 1 | 2 | 3;
|
|
3
4
|
export interface LaunchpadWidgetConfig {
|
|
5
|
+
/** Launchpad group ids to show. Omitted on legacy widgets means all groups. */
|
|
6
|
+
groupIds?: string[];
|
|
4
7
|
}
|
|
5
8
|
export interface ClockWidgetConfig {
|
|
6
9
|
timeZone: string;
|
|
7
10
|
preset: string;
|
|
8
11
|
/** Display the time on a 12-hour clock. Defaults to 24-hour when absent. */
|
|
9
12
|
hour12?: boolean;
|
|
13
|
+
/** Resolved place. Legacy widgets have only timeZone. */
|
|
14
|
+
location?: GeoLocation;
|
|
10
15
|
}
|
|
11
16
|
export interface WebpageWidgetConfig {
|
|
12
17
|
url: string;
|
|
13
18
|
}
|
|
14
|
-
export
|
|
19
|
+
export interface AnalyticsWidgetConfig {
|
|
20
|
+
/** Saved GA4 property resource name, e.g. `properties/123456`. */
|
|
21
|
+
propertyId: string;
|
|
22
|
+
}
|
|
23
|
+
export declare const UTILITY_TOOL_IDS: readonly ["unit-converter", "text-workbench", "date-calculator"];
|
|
24
|
+
export type UtilityToolId = (typeof UTILITY_TOOL_IDS)[number];
|
|
25
|
+
/**
|
|
26
|
+
* Persisted utility selection and defaults. Interactive values remain local
|
|
27
|
+
* to the widget and are deliberately not written on every keystroke.
|
|
28
|
+
*/
|
|
29
|
+
export interface UtilityWidgetConfig {
|
|
30
|
+
schemaVersion: 1;
|
|
31
|
+
toolId: UtilityToolId;
|
|
32
|
+
settings: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
export interface NewsWidgetConfig {
|
|
35
|
+
/** Enabled provider to use. Omitted means the category default. */
|
|
36
|
+
providerId?: string;
|
|
37
|
+
limit: number;
|
|
38
|
+
/** Automatic feed refresh cadence in minutes. Omitted means 15 minutes. */
|
|
39
|
+
refreshMinutes?: number;
|
|
40
|
+
}
|
|
41
|
+
export interface WeatherWidgetConfig {
|
|
42
|
+
/** Enabled provider to use. Omitted means the category default. */
|
|
43
|
+
providerId?: string;
|
|
44
|
+
/** Resolved place; plain string on legacy widgets. */
|
|
45
|
+
location: string | GeoLocation;
|
|
46
|
+
}
|
|
47
|
+
export interface CurrencyWidgetConfig {
|
|
48
|
+
/** Enabled provider to use. Omitted means the category default. */
|
|
49
|
+
providerId?: string;
|
|
50
|
+
base: string;
|
|
51
|
+
quote: string;
|
|
52
|
+
defaultAmount: number;
|
|
53
|
+
}
|
|
54
|
+
export type HomeWidgetConfig = LaunchpadWidgetConfig | ClockWidgetConfig | WebpageWidgetConfig | AnalyticsWidgetConfig | UtilityWidgetConfig | NewsWidgetConfig | WeatherWidgetConfig | CurrencyWidgetConfig;
|
|
15
55
|
export interface HomeDashboardWidget {
|
|
16
56
|
id: string;
|
|
57
|
+
/** Optional user-defined chrome title. The widget type is used when omitted. */
|
|
58
|
+
title?: string;
|
|
17
59
|
type: HomeWidgetType;
|
|
18
60
|
colSpan: HomeWidgetColSpan;
|
|
19
61
|
order: number;
|
|
@@ -27,6 +69,8 @@ export declare const HOME_WIDGET_TYPES: readonly HomeWidgetType[];
|
|
|
27
69
|
export declare const MAX_HOME_WIDGETS = 30;
|
|
28
70
|
export declare const CLOCK_DEFAULT_TIMEZONE = "America/Sao_Paulo";
|
|
29
71
|
export declare const CLOCK_DEFAULT_PRESET = "aurora";
|
|
30
|
-
export declare const CLOCK_PRESETS: readonly ["aurora", "grid", "binary"];
|
|
72
|
+
export declare const CLOCK_PRESETS: readonly ["aurora", "grid", "binary", "day-night", "location-photos"];
|
|
31
73
|
export declare function isHomeWidgetType(value: string): value is HomeWidgetType;
|
|
74
|
+
export type HomeWidgetConfigValidationResult = string | null;
|
|
75
|
+
export declare function validateHomeWidgetConfig(type: HomeWidgetType, config: unknown): HomeWidgetConfigValidationResult;
|
|
32
76
|
//# sourceMappingURL=home-dashboard.types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"home-dashboard.types.d.ts","sourceRoot":"","sources":["../src/home-dashboard.types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,
|
|
1
|
+
{"version":3,"file":"home-dashboard.types.d.ts","sourceRoot":"","sources":["../src/home-dashboard.types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,16 +1,192 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CLOCK_PRESETS = exports.CLOCK_DEFAULT_PRESET = exports.CLOCK_DEFAULT_TIMEZONE = exports.MAX_HOME_WIDGETS = exports.HOME_WIDGET_TYPES = void 0;
|
|
3
|
+
exports.CLOCK_PRESETS = exports.CLOCK_DEFAULT_PRESET = exports.CLOCK_DEFAULT_TIMEZONE = exports.MAX_HOME_WIDGETS = exports.HOME_WIDGET_TYPES = exports.UTILITY_TOOL_IDS = void 0;
|
|
4
4
|
exports.isHomeWidgetType = isHomeWidgetType;
|
|
5
|
+
exports.validateHomeWidgetConfig = validateHomeWidgetConfig;
|
|
6
|
+
exports.UTILITY_TOOL_IDS = [
|
|
7
|
+
"unit-converter",
|
|
8
|
+
"text-workbench",
|
|
9
|
+
"date-calculator",
|
|
10
|
+
];
|
|
5
11
|
exports.HOME_WIDGET_TYPES = [
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
12
|
+
"launchpad",
|
|
13
|
+
"clock",
|
|
14
|
+
"webpage",
|
|
15
|
+
"analytics",
|
|
16
|
+
"utility",
|
|
17
|
+
"news",
|
|
18
|
+
"weather",
|
|
19
|
+
"currency",
|
|
9
20
|
];
|
|
10
21
|
exports.MAX_HOME_WIDGETS = 30;
|
|
11
|
-
exports.CLOCK_DEFAULT_TIMEZONE =
|
|
12
|
-
exports.CLOCK_DEFAULT_PRESET =
|
|
13
|
-
exports.CLOCK_PRESETS = [
|
|
22
|
+
exports.CLOCK_DEFAULT_TIMEZONE = "America/Sao_Paulo";
|
|
23
|
+
exports.CLOCK_DEFAULT_PRESET = "aurora";
|
|
24
|
+
exports.CLOCK_PRESETS = [
|
|
25
|
+
"aurora",
|
|
26
|
+
"grid",
|
|
27
|
+
"binary",
|
|
28
|
+
"day-night",
|
|
29
|
+
"location-photos",
|
|
30
|
+
];
|
|
14
31
|
function isHomeWidgetType(value) {
|
|
15
32
|
return exports.HOME_WIDGET_TYPES.includes(value);
|
|
16
33
|
}
|
|
34
|
+
function configRecord(config) {
|
|
35
|
+
return typeof config === "object" && config !== null && !Array.isArray(config)
|
|
36
|
+
? config
|
|
37
|
+
: null;
|
|
38
|
+
}
|
|
39
|
+
function providerIdError(config) {
|
|
40
|
+
const providerId = config.providerId;
|
|
41
|
+
return providerId !== undefined &&
|
|
42
|
+
(typeof providerId !== "string" ||
|
|
43
|
+
providerId.trim().length === 0 ||
|
|
44
|
+
providerId.length > 200)
|
|
45
|
+
? "widget providerId must be a non-empty string"
|
|
46
|
+
: null;
|
|
47
|
+
}
|
|
48
|
+
const HOME_WIDGET_CONFIG_VALIDATORS = {
|
|
49
|
+
launchpad(config) {
|
|
50
|
+
const value = configRecord(config);
|
|
51
|
+
if (!value)
|
|
52
|
+
return "launchpad widget config must be an object";
|
|
53
|
+
const ids = value.groupIds;
|
|
54
|
+
if (ids !== undefined &&
|
|
55
|
+
(!Array.isArray(ids) ||
|
|
56
|
+
ids.length === 0 ||
|
|
57
|
+
ids.some((id) => typeof id !== "string" || id.trim().length === 0) ||
|
|
58
|
+
new Set(ids).size !== ids.length)) {
|
|
59
|
+
return "launchpad widget config groupIds must be a non-empty array of unique strings";
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
},
|
|
63
|
+
clock(config) {
|
|
64
|
+
const value = configRecord(config);
|
|
65
|
+
if (!value ||
|
|
66
|
+
typeof value.timeZone !== "string" ||
|
|
67
|
+
value.timeZone.length === 0 ||
|
|
68
|
+
typeof value.preset !== "string" ||
|
|
69
|
+
value.preset.length === 0) {
|
|
70
|
+
return "clock widget config requires timeZone and preset";
|
|
71
|
+
}
|
|
72
|
+
return value.hour12 !== undefined && typeof value.hour12 !== "boolean"
|
|
73
|
+
? "clock widget hour12 must be a boolean"
|
|
74
|
+
: null;
|
|
75
|
+
},
|
|
76
|
+
webpage(config) {
|
|
77
|
+
const value = configRecord(config);
|
|
78
|
+
if (!value || typeof value.url !== "string") {
|
|
79
|
+
return "webpage widget config url must be a string";
|
|
80
|
+
}
|
|
81
|
+
return value.url.length > 0 && !/^https?:\/\//i.test(value.url)
|
|
82
|
+
? "webpage widget config url must start with http:// or https://"
|
|
83
|
+
: null;
|
|
84
|
+
},
|
|
85
|
+
analytics(config) {
|
|
86
|
+
const propertyId = configRecord(config)?.propertyId;
|
|
87
|
+
return typeof propertyId !== "string" ||
|
|
88
|
+
!/^properties\/\d+$/.test(propertyId)
|
|
89
|
+
? "analytics widget config propertyId must look like properties/123456"
|
|
90
|
+
: null;
|
|
91
|
+
},
|
|
92
|
+
utility(config) {
|
|
93
|
+
const value = configRecord(config);
|
|
94
|
+
if (!value)
|
|
95
|
+
return "utility widget config must be an object";
|
|
96
|
+
if (value.schemaVersion !== 1)
|
|
97
|
+
return "utility widget config schemaVersion must be 1";
|
|
98
|
+
if (typeof value.toolId !== "string" ||
|
|
99
|
+
!exports.UTILITY_TOOL_IDS.includes(value.toolId)) {
|
|
100
|
+
return "utility widget config contains an unsupported toolId";
|
|
101
|
+
}
|
|
102
|
+
const settings = configRecord(value.settings);
|
|
103
|
+
if (!settings)
|
|
104
|
+
return "utility widget settings must be an object";
|
|
105
|
+
return new TextEncoder().encode(JSON.stringify(settings)).length > 4096
|
|
106
|
+
? "utility widget settings are too large"
|
|
107
|
+
: null;
|
|
108
|
+
},
|
|
109
|
+
news(config) {
|
|
110
|
+
const value = configRecord(config);
|
|
111
|
+
if (!value)
|
|
112
|
+
return "news widget config must be an object";
|
|
113
|
+
const providerError = providerIdError(value);
|
|
114
|
+
if (providerError)
|
|
115
|
+
return providerError;
|
|
116
|
+
if (typeof value.limit !== "number" ||
|
|
117
|
+
!Number.isInteger(value.limit) ||
|
|
118
|
+
value.limit < 1 ||
|
|
119
|
+
value.limit > 12) {
|
|
120
|
+
return "news widget limit must be an integer from 1 to 12";
|
|
121
|
+
}
|
|
122
|
+
const refresh = value.refreshMinutes;
|
|
123
|
+
return refresh !== undefined &&
|
|
124
|
+
(typeof refresh !== "number" ||
|
|
125
|
+
!Number.isInteger(refresh) ||
|
|
126
|
+
refresh < 1 ||
|
|
127
|
+
refresh > 1440)
|
|
128
|
+
? "news widget refreshMinutes must be an integer from 1 to 1440"
|
|
129
|
+
: null;
|
|
130
|
+
},
|
|
131
|
+
weather(config) {
|
|
132
|
+
const value = configRecord(config);
|
|
133
|
+
if (!value)
|
|
134
|
+
return "weather widget requires a valid location";
|
|
135
|
+
const providerError = providerIdError(value);
|
|
136
|
+
if (providerError)
|
|
137
|
+
return providerError;
|
|
138
|
+
const location = value.location;
|
|
139
|
+
const legacy = typeof location === "string" &&
|
|
140
|
+
location.trim().length >= 2 &&
|
|
141
|
+
location.length <= 120;
|
|
142
|
+
const resolved = configRecord(location);
|
|
143
|
+
const valid = resolved !== null &&
|
|
144
|
+
typeof resolved.name === "string" &&
|
|
145
|
+
resolved.name.trim().length > 0 &&
|
|
146
|
+
resolved.name.length <= 120 &&
|
|
147
|
+
typeof resolved.lat === "number" &&
|
|
148
|
+
Number.isFinite(resolved.lat) &&
|
|
149
|
+
resolved.lat >= -90 &&
|
|
150
|
+
resolved.lat <= 90 &&
|
|
151
|
+
typeof resolved.lon === "number" &&
|
|
152
|
+
Number.isFinite(resolved.lon) &&
|
|
153
|
+
resolved.lon >= -180 &&
|
|
154
|
+
resolved.lon <= 180 &&
|
|
155
|
+
typeof resolved.timeZone === "string" &&
|
|
156
|
+
resolved.timeZone.trim().length > 0 &&
|
|
157
|
+
resolved.timeZone.length <= 120;
|
|
158
|
+
return legacy || valid ? null : "weather widget requires a valid location";
|
|
159
|
+
},
|
|
160
|
+
currency(config) {
|
|
161
|
+
const value = configRecord(config);
|
|
162
|
+
if (!value)
|
|
163
|
+
return "currency widget config is invalid";
|
|
164
|
+
const providerError = providerIdError(value);
|
|
165
|
+
if (providerError)
|
|
166
|
+
return providerError;
|
|
167
|
+
if ("currencies" in value || "home" in value) {
|
|
168
|
+
const currencies = value.currencies;
|
|
169
|
+
return typeof value.home !== "string" ||
|
|
170
|
+
!/^[A-Z]{3}$/.test(value.home) ||
|
|
171
|
+
!Array.isArray(currencies) ||
|
|
172
|
+
currencies.length < 1 ||
|
|
173
|
+
currencies.length > 3 ||
|
|
174
|
+
currencies.some((code) => typeof code !== "string" || !/^[A-Z]{3}$/.test(code)) ||
|
|
175
|
+
new Set(currencies).size !== currencies.length
|
|
176
|
+
? "currency widget requires a home currency and 1 to 3 unique currencies"
|
|
177
|
+
: null;
|
|
178
|
+
}
|
|
179
|
+
return typeof value.base !== "string" ||
|
|
180
|
+
typeof value.quote !== "string" ||
|
|
181
|
+
!/^[A-Z]{3}$/.test(value.base) ||
|
|
182
|
+
!/^[A-Z]{3}$/.test(value.quote) ||
|
|
183
|
+
typeof value.defaultAmount !== "number" ||
|
|
184
|
+
!Number.isFinite(value.defaultAmount) ||
|
|
185
|
+
value.defaultAmount < 0
|
|
186
|
+
? "currency widget requires base, quote, and defaultAmount"
|
|
187
|
+
: null;
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
function validateHomeWidgetConfig(type, config) {
|
|
191
|
+
return HOME_WIDGET_CONFIG_VALIDATORS[type](config);
|
|
192
|
+
}
|
|
@@ -2,33 +2,160 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const website_firestore_types_1 = require("./website-firestore.types");
|
|
4
4
|
const home_dashboard_types_1 = require("./home-dashboard.types");
|
|
5
|
-
describe(
|
|
6
|
-
it(
|
|
7
|
-
expect(website_firestore_types_1.FIRESTORE_COLLECTIONS.HOME_DASHBOARDS).toBe(
|
|
5
|
+
describe("home dashboard types", () => {
|
|
6
|
+
it("registers the Firestore collection", () => {
|
|
7
|
+
expect(website_firestore_types_1.FIRESTORE_COLLECTIONS.HOME_DASHBOARDS).toBe("home-dashboards");
|
|
8
8
|
});
|
|
9
|
-
it(
|
|
10
|
-
expect(home_dashboard_types_1.HOME_WIDGET_TYPES).toEqual([
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
it("accepts the shipped types and rejects unknown ones", () => {
|
|
10
|
+
expect(home_dashboard_types_1.HOME_WIDGET_TYPES).toEqual([
|
|
11
|
+
"launchpad",
|
|
12
|
+
"clock",
|
|
13
|
+
"webpage",
|
|
14
|
+
"analytics",
|
|
15
|
+
"utility",
|
|
16
|
+
"news",
|
|
17
|
+
"weather",
|
|
18
|
+
"currency",
|
|
19
|
+
]);
|
|
20
|
+
expect((0, home_dashboard_types_1.isHomeWidgetType)("clock")).toBe(true);
|
|
21
|
+
expect((0, home_dashboard_types_1.isHomeWidgetType)("analytics")).toBe(true);
|
|
22
|
+
expect((0, home_dashboard_types_1.isHomeWidgetType)("utility")).toBe(true);
|
|
23
|
+
expect((0, home_dashboard_types_1.isHomeWidgetType)("weather")).toBe(true);
|
|
24
|
+
expect((0, home_dashboard_types_1.isHomeWidgetType)("stock-ticker")).toBe(false);
|
|
13
25
|
});
|
|
14
|
-
it(
|
|
26
|
+
it("stores a versioned allowlisted utility configuration", () => {
|
|
27
|
+
const widget = {
|
|
28
|
+
id: "utility-1",
|
|
29
|
+
type: "utility",
|
|
30
|
+
colSpan: 2,
|
|
31
|
+
order: 0,
|
|
32
|
+
config: {
|
|
33
|
+
schemaVersion: 1,
|
|
34
|
+
toolId: "unit-converter",
|
|
35
|
+
settings: {
|
|
36
|
+
category: "length",
|
|
37
|
+
from: "meter",
|
|
38
|
+
to: "foot",
|
|
39
|
+
precision: 2,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
expect(widget.config).toMatchObject({
|
|
44
|
+
schemaVersion: 1,
|
|
45
|
+
toolId: "unit-converter",
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
it("caps a board at 30 widgets and defaults the clock", () => {
|
|
15
49
|
expect(home_dashboard_types_1.MAX_HOME_WIDGETS).toBe(30);
|
|
16
|
-
expect(home_dashboard_types_1.CLOCK_DEFAULT_TIMEZONE).toBe(
|
|
17
|
-
expect(home_dashboard_types_1.CLOCK_DEFAULT_PRESET).toBe(
|
|
50
|
+
expect(home_dashboard_types_1.CLOCK_DEFAULT_TIMEZONE).toBe("America/Sao_Paulo");
|
|
51
|
+
expect(home_dashboard_types_1.CLOCK_DEFAULT_PRESET).toBe("aurora");
|
|
52
|
+
});
|
|
53
|
+
it("allows launchpad widgets to select groups while keeping legacy config valid", () => {
|
|
54
|
+
const legacy = {};
|
|
55
|
+
const selected = {
|
|
56
|
+
groupIds: ["infra", "publishing"],
|
|
57
|
+
};
|
|
58
|
+
expect(legacy.groupIds).toBeUndefined();
|
|
59
|
+
expect(selected.groupIds).toEqual(["infra", "publishing"]);
|
|
60
|
+
});
|
|
61
|
+
it("allows a widget to carry a custom title", () => {
|
|
62
|
+
const widget = {
|
|
63
|
+
id: "clock-1",
|
|
64
|
+
title: "São Paulo",
|
|
65
|
+
type: "clock",
|
|
66
|
+
colSpan: 1,
|
|
67
|
+
order: 0,
|
|
68
|
+
config: { timeZone: "America/Sao_Paulo", preset: "aurora" },
|
|
69
|
+
};
|
|
70
|
+
expect(widget.title).toBe("São Paulo");
|
|
71
|
+
});
|
|
72
|
+
it("stores the selected property on an Analytics widget", () => {
|
|
73
|
+
const widget = {
|
|
74
|
+
id: "analytics-1",
|
|
75
|
+
type: "analytics",
|
|
76
|
+
colSpan: 2,
|
|
77
|
+
order: 0,
|
|
78
|
+
config: { propertyId: "properties/123456" },
|
|
79
|
+
};
|
|
80
|
+
expect(widget.config).toEqual({ propertyId: "properties/123456" });
|
|
81
|
+
});
|
|
82
|
+
it("stores a news refresh cadence while keeping legacy configs valid", () => {
|
|
83
|
+
const legacy = { limit: 5 };
|
|
84
|
+
const refreshing = { limit: 5, refreshMinutes: 30 };
|
|
85
|
+
expect(legacy.refreshMinutes).toBeUndefined();
|
|
86
|
+
expect(refreshing.refreshMinutes).toBe(30);
|
|
18
87
|
});
|
|
19
88
|
});
|
|
20
|
-
describe(
|
|
21
|
-
it(
|
|
22
|
-
expect(home_dashboard_types_1.CLOCK_PRESETS).toContain(
|
|
89
|
+
describe("clock presets", () => {
|
|
90
|
+
it("includes the location-aware backgrounds", () => {
|
|
91
|
+
expect(home_dashboard_types_1.CLOCK_PRESETS).toContain("day-night");
|
|
92
|
+
expect(home_dashboard_types_1.CLOCK_PRESETS).toContain("location-photos");
|
|
93
|
+
});
|
|
94
|
+
it("includes the binary preset", () => {
|
|
95
|
+
expect(home_dashboard_types_1.CLOCK_PRESETS).toContain("binary");
|
|
23
96
|
});
|
|
24
|
-
it(
|
|
25
|
-
expect(home_dashboard_types_1.CLOCK_DEFAULT_PRESET).toBe(
|
|
97
|
+
it("keeps aurora as the default and a member of the list", () => {
|
|
98
|
+
expect(home_dashboard_types_1.CLOCK_DEFAULT_PRESET).toBe("aurora");
|
|
26
99
|
expect(home_dashboard_types_1.CLOCK_PRESETS).toContain(home_dashboard_types_1.CLOCK_DEFAULT_PRESET);
|
|
27
100
|
});
|
|
28
|
-
it(
|
|
29
|
-
const twentyFour = { timeZone:
|
|
30
|
-
const twelve = {
|
|
101
|
+
it("allows an optional hour12 flag on a clock config", () => {
|
|
102
|
+
const twentyFour = { timeZone: "UTC", preset: "grid" };
|
|
103
|
+
const twelve = {
|
|
104
|
+
timeZone: "UTC",
|
|
105
|
+
preset: "grid",
|
|
106
|
+
hour12: true,
|
|
107
|
+
};
|
|
31
108
|
expect(twentyFour.hour12).toBeUndefined();
|
|
32
109
|
expect(twelve.hour12).toBe(true);
|
|
33
110
|
});
|
|
34
111
|
});
|
|
112
|
+
describe("GeoLocation", () => {
|
|
113
|
+
it("accepts a resolved place and is assignable into widget configs", () => {
|
|
114
|
+
const loc = {
|
|
115
|
+
name: "São José da Coroa Grande",
|
|
116
|
+
region: "Pernambuco, Brazil",
|
|
117
|
+
lat: -8.8978,
|
|
118
|
+
lon: -35.1478,
|
|
119
|
+
timeZone: "America/Recife",
|
|
120
|
+
placeId: "ChIJexample",
|
|
121
|
+
};
|
|
122
|
+
const clock = {
|
|
123
|
+
timeZone: loc.timeZone,
|
|
124
|
+
preset: "aurora",
|
|
125
|
+
location: loc,
|
|
126
|
+
};
|
|
127
|
+
const weather = { location: loc };
|
|
128
|
+
expect(clock.location?.lat).toBeCloseTo(-8.8978);
|
|
129
|
+
expect(typeof weather.location === "string" ? "" : weather.location.name).toContain("Coroa");
|
|
130
|
+
});
|
|
131
|
+
it("weather still accepts a legacy string location", () => {
|
|
132
|
+
const weather = { location: "Cayenne" };
|
|
133
|
+
expect(weather.location).toBe("Cayenne");
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
describe("home widget config validation", () => {
|
|
137
|
+
it("uses one shared validator for current and legacy currency configs", () => {
|
|
138
|
+
expect((0, home_dashboard_types_1.validateHomeWidgetConfig)("currency", {
|
|
139
|
+
home: "BRL",
|
|
140
|
+
currencies: ["USD", "EUR"],
|
|
141
|
+
})).toBeNull();
|
|
142
|
+
expect((0, home_dashboard_types_1.validateHomeWidgetConfig)("currency", {
|
|
143
|
+
base: "USD",
|
|
144
|
+
quote: "EUR",
|
|
145
|
+
defaultAmount: 1,
|
|
146
|
+
})).toBeNull();
|
|
147
|
+
expect((0, home_dashboard_types_1.validateHomeWidgetConfig)("currency", {
|
|
148
|
+
home: "BRL",
|
|
149
|
+
currencies: ["USD", "USD"],
|
|
150
|
+
})).toContain("unique currencies");
|
|
151
|
+
});
|
|
152
|
+
it("applies resolved-location constraints consistently", () => {
|
|
153
|
+
expect((0, home_dashboard_types_1.validateHomeWidgetConfig)("weather", {
|
|
154
|
+
location: { name: "Recife", lat: -8.05, lon: -34.9 },
|
|
155
|
+
})).toBe("weather widget requires a valid location");
|
|
156
|
+
});
|
|
157
|
+
it("keeps an empty webpage placeholder but rejects a missing URL", () => {
|
|
158
|
+
expect((0, home_dashboard_types_1.validateHomeWidgetConfig)("webpage", { url: "" })).toBeNull();
|
|
159
|
+
expect((0, home_dashboard_types_1.validateHomeWidgetConfig)("webpage", {})).toBe("webpage widget config url must be a string");
|
|
160
|
+
});
|
|
161
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -22,8 +22,10 @@ export * from './agent.types';
|
|
|
22
22
|
export * from './file.types';
|
|
23
23
|
export * from './launchpad.types';
|
|
24
24
|
export * from './home-dashboard.types';
|
|
25
|
+
export * from './geo.types';
|
|
25
26
|
export * from './email.types';
|
|
26
27
|
export * from './analytics.types';
|
|
28
|
+
export * from './widget-services.types';
|
|
27
29
|
export type { Security, SecurityChallengeOptions, SecurityChallengeResult } from './security.interface';
|
|
28
30
|
export { SecurityLevel } from './security.interface';
|
|
29
31
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,cAAc,cAAc,CAAC;AAG7B,cAAc,gBAAgB,CAAC;AAG/B,cAAc,gBAAgB,CAAC;AAG/B,cAAc,iBAAiB,CAAC;AAGhC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,2BAA2B,CAAC;AAG1C,cAAc,cAAc,CAAC;AAG7B,cAAc,4BAA4B,CAAC;AAG3C,cAAc,gBAAgB,CAAC;AAG/B,cAAc,cAAc,CAAC;AAG7B,cAAc,eAAe,CAAC;AAG9B,cAAc,eAAe,CAAC;AAG9B,cAAc,cAAc,CAAC;AAG7B,cAAc,mBAAmB,CAAC;AAGlC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,eAAe,CAAC;AAG9B,cAAc,mBAAmB,CAAC;AAGlC,YAAY,EAAE,QAAQ,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACxG,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,cAAc,cAAc,CAAC;AAG7B,cAAc,gBAAgB,CAAC;AAG/B,cAAc,gBAAgB,CAAC;AAG/B,cAAc,iBAAiB,CAAC;AAGhC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,2BAA2B,CAAC;AAG1C,cAAc,cAAc,CAAC;AAG7B,cAAc,4BAA4B,CAAC;AAG3C,cAAc,gBAAgB,CAAC;AAG/B,cAAc,cAAc,CAAC;AAG7B,cAAc,eAAe,CAAC;AAG9B,cAAc,eAAe,CAAC;AAG9B,cAAc,cAAc,CAAC;AAG7B,cAAc,mBAAmB,CAAC;AAGlC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,aAAa,CAAC;AAG5B,cAAc,eAAe,CAAC;AAG9B,cAAc,mBAAmB,CAAC;AAGlC,cAAc,yBAAyB,CAAC;AAGxC,YAAY,EAAE,QAAQ,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACxG,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -54,9 +54,13 @@ __exportStar(require("./file.types"), exports);
|
|
|
54
54
|
__exportStar(require("./launchpad.types"), exports);
|
|
55
55
|
// Per-user home dashboard widget types
|
|
56
56
|
__exportStar(require("./home-dashboard.types"), exports);
|
|
57
|
+
// Geo autocomplete types
|
|
58
|
+
__exportStar(require("./geo.types"), exports);
|
|
57
59
|
// Email module types
|
|
58
60
|
__exportStar(require("./email.types"), exports);
|
|
59
61
|
// Google Analytics portal types
|
|
60
62
|
__exportStar(require("./analytics.types"), exports);
|
|
63
|
+
// Provider-backed dashboard widget types
|
|
64
|
+
__exportStar(require("./widget-services.types"), exports);
|
|
61
65
|
var security_interface_1 = require("./security.interface");
|
|
62
66
|
Object.defineProperty(exports, "SecurityLevel", { enumerable: true, get: function () { return security_interface_1.SecurityLevel; } });
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/** Shared contracts for provider-backed dashboard widgets. */
|
|
2
|
+
export type WidgetServiceKind = "news" | "weather" | "currency" | "geo";
|
|
3
|
+
export type WidgetServiceSettingValue = string | number | boolean;
|
|
4
|
+
export type WidgetServiceSettings = Record<string, WidgetServiceSettingValue>;
|
|
5
|
+
export interface WidgetServiceProviderSettingsMap {
|
|
6
|
+
rss: {
|
|
7
|
+
feedUrl: string;
|
|
8
|
+
};
|
|
9
|
+
"open-meteo": Record<string, never>;
|
|
10
|
+
frankfurter: Record<string, never>;
|
|
11
|
+
"google-places": {
|
|
12
|
+
apiKey: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export type WidgetServiceProviderType = keyof WidgetServiceProviderSettingsMap;
|
|
16
|
+
export interface WidgetServiceSettingsField {
|
|
17
|
+
key: string;
|
|
18
|
+
label: string;
|
|
19
|
+
input: "text" | "url" | "password";
|
|
20
|
+
required: boolean;
|
|
21
|
+
maxLength: number;
|
|
22
|
+
defaultValue?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface WidgetServiceProviderDefinition<T extends WidgetServiceProviderType> {
|
|
25
|
+
type: T;
|
|
26
|
+
kind: WidgetServiceKind;
|
|
27
|
+
label: string;
|
|
28
|
+
settingsFields: readonly WidgetServiceSettingsField[];
|
|
29
|
+
secretSettingKeys: readonly (keyof WidgetServiceProviderSettingsMap[T] & string)[];
|
|
30
|
+
normalizeSettings(settings: WidgetServiceSettings): WidgetServiceProviderSettingsMap[T];
|
|
31
|
+
}
|
|
32
|
+
export declare const WIDGET_SERVICE_SECRET_MASK = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
33
|
+
declare function noSettings(): Record<string, never>;
|
|
34
|
+
declare function normalizeRssSettings(settings: WidgetServiceSettings): {
|
|
35
|
+
feedUrl: string;
|
|
36
|
+
};
|
|
37
|
+
declare function normalizeGooglePlacesSettings(settings: WidgetServiceSettings): {
|
|
38
|
+
apiKey: string;
|
|
39
|
+
};
|
|
40
|
+
/** Single registration point for provider metadata and per-type settings schemas. */
|
|
41
|
+
export declare const WIDGET_SERVICE_PROVIDER_DEFINITIONS: {
|
|
42
|
+
rss: {
|
|
43
|
+
type: "rss";
|
|
44
|
+
kind: "news";
|
|
45
|
+
label: string;
|
|
46
|
+
settingsFields: {
|
|
47
|
+
key: string;
|
|
48
|
+
label: string;
|
|
49
|
+
input: "url";
|
|
50
|
+
required: true;
|
|
51
|
+
maxLength: number;
|
|
52
|
+
defaultValue: string;
|
|
53
|
+
}[];
|
|
54
|
+
secretSettingKeys: never[];
|
|
55
|
+
normalizeSettings: typeof normalizeRssSettings;
|
|
56
|
+
};
|
|
57
|
+
"open-meteo": {
|
|
58
|
+
type: "open-meteo";
|
|
59
|
+
kind: "weather";
|
|
60
|
+
label: string;
|
|
61
|
+
settingsFields: never[];
|
|
62
|
+
secretSettingKeys: never[];
|
|
63
|
+
normalizeSettings: typeof noSettings;
|
|
64
|
+
};
|
|
65
|
+
frankfurter: {
|
|
66
|
+
type: "frankfurter";
|
|
67
|
+
kind: "currency";
|
|
68
|
+
label: string;
|
|
69
|
+
settingsFields: never[];
|
|
70
|
+
secretSettingKeys: never[];
|
|
71
|
+
normalizeSettings: typeof noSettings;
|
|
72
|
+
};
|
|
73
|
+
"google-places": {
|
|
74
|
+
type: "google-places";
|
|
75
|
+
kind: "geo";
|
|
76
|
+
label: string;
|
|
77
|
+
settingsFields: {
|
|
78
|
+
key: string;
|
|
79
|
+
label: string;
|
|
80
|
+
input: "password";
|
|
81
|
+
required: true;
|
|
82
|
+
maxLength: number;
|
|
83
|
+
}[];
|
|
84
|
+
secretSettingKeys: "apiKey"[];
|
|
85
|
+
normalizeSettings: typeof normalizeGooglePlacesSettings;
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
export declare const WIDGET_SERVICE_PROVIDER_TYPES: readonly (keyof WidgetServiceProviderSettingsMap)[];
|
|
89
|
+
export declare function normalizeWidgetServiceProviderSettings<T extends WidgetServiceProviderType>(type: T, settings: WidgetServiceSettings): WidgetServiceProviderSettingsMap[T];
|
|
90
|
+
export type WidgetServiceResult<T> = {
|
|
91
|
+
ok: true;
|
|
92
|
+
value: T;
|
|
93
|
+
message?: string;
|
|
94
|
+
} | {
|
|
95
|
+
ok: false;
|
|
96
|
+
error: string;
|
|
97
|
+
};
|
|
98
|
+
export interface WidgetServiceProviderConfig {
|
|
99
|
+
id: string;
|
|
100
|
+
kind: WidgetServiceKind;
|
|
101
|
+
type: WidgetServiceProviderType;
|
|
102
|
+
name: string;
|
|
103
|
+
enabled: boolean;
|
|
104
|
+
active: boolean;
|
|
105
|
+
settings: WidgetServiceSettings;
|
|
106
|
+
}
|
|
107
|
+
export interface WidgetServiceProviderPayload {
|
|
108
|
+
type: WidgetServiceProviderType;
|
|
109
|
+
name: string;
|
|
110
|
+
enabled: boolean;
|
|
111
|
+
settings: WidgetServiceSettings;
|
|
112
|
+
}
|
|
113
|
+
export interface NewsArticle {
|
|
114
|
+
title: string;
|
|
115
|
+
description: string;
|
|
116
|
+
url: string;
|
|
117
|
+
imageUrl?: string;
|
|
118
|
+
publishedAt?: string;
|
|
119
|
+
source: string;
|
|
120
|
+
}
|
|
121
|
+
export interface NewsFeed {
|
|
122
|
+
source: string;
|
|
123
|
+
articles: NewsArticle[];
|
|
124
|
+
fetchedAt: string;
|
|
125
|
+
}
|
|
126
|
+
export interface WeatherSnapshot {
|
|
127
|
+
location: string;
|
|
128
|
+
latitude: number;
|
|
129
|
+
longitude: number;
|
|
130
|
+
temperatureC: number;
|
|
131
|
+
apparentTemperatureC: number;
|
|
132
|
+
humidityPercent: number;
|
|
133
|
+
precipitationMm: number;
|
|
134
|
+
pressureHpa: number;
|
|
135
|
+
cloudCoverPercent: number;
|
|
136
|
+
windSpeedKph: number;
|
|
137
|
+
windDirectionDegrees: number;
|
|
138
|
+
weatherCode: number;
|
|
139
|
+
description: string;
|
|
140
|
+
observedAt: string;
|
|
141
|
+
}
|
|
142
|
+
export interface CurrencyRate {
|
|
143
|
+
base: string;
|
|
144
|
+
quote: string;
|
|
145
|
+
rate: number;
|
|
146
|
+
date: string;
|
|
147
|
+
}
|
|
148
|
+
export {};
|
|
149
|
+
//# sourceMappingURL=widget-services.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget-services.types.d.ts","sourceRoot":"","sources":["../src/widget-services.types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WIDGET_SERVICE_PROVIDER_TYPES = exports.WIDGET_SERVICE_PROVIDER_DEFINITIONS = exports.WIDGET_SERVICE_SECRET_MASK = void 0;
|
|
4
|
+
exports.normalizeWidgetServiceProviderSettings = normalizeWidgetServiceProviderSettings;
|
|
5
|
+
exports.WIDGET_SERVICE_SECRET_MASK = "••••••••";
|
|
6
|
+
function noSettings() {
|
|
7
|
+
return {};
|
|
8
|
+
}
|
|
9
|
+
function normalizeRssSettings(settings) {
|
|
10
|
+
const feedUrl = typeof settings.feedUrl === "string" ? settings.feedUrl.trim() : "";
|
|
11
|
+
let parsed;
|
|
12
|
+
try {
|
|
13
|
+
parsed = new URL(feedUrl);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
throw new Error("RSS feed URL must be a valid HTTPS URL");
|
|
17
|
+
}
|
|
18
|
+
if (parsed.protocol !== "https:")
|
|
19
|
+
throw new Error("RSS feed URL must use HTTPS");
|
|
20
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
21
|
+
if (hostname === "localhost" ||
|
|
22
|
+
hostname.endsWith(".localhost") ||
|
|
23
|
+
hostname.endsWith(".local") ||
|
|
24
|
+
hostname === "::1" ||
|
|
25
|
+
/^127\./.test(hostname) ||
|
|
26
|
+
/^10\./.test(hostname) ||
|
|
27
|
+
/^192\.168\./.test(hostname) ||
|
|
28
|
+
/^169\.254\./.test(hostname) ||
|
|
29
|
+
/^172\.(1[6-9]|2\d|3[01])\./.test(hostname)) {
|
|
30
|
+
throw new Error("RSS feed URL cannot target a local or private host");
|
|
31
|
+
}
|
|
32
|
+
return { feedUrl: parsed.toString() };
|
|
33
|
+
}
|
|
34
|
+
function normalizeGooglePlacesSettings(settings) {
|
|
35
|
+
const apiKey = typeof settings.apiKey === "string" ? settings.apiKey.trim() : "";
|
|
36
|
+
if (!apiKey || apiKey === exports.WIDGET_SERVICE_SECRET_MASK) {
|
|
37
|
+
throw new Error("Google Places API key is required");
|
|
38
|
+
}
|
|
39
|
+
return { apiKey };
|
|
40
|
+
}
|
|
41
|
+
/** Single registration point for provider metadata and per-type settings schemas. */
|
|
42
|
+
exports.WIDGET_SERVICE_PROVIDER_DEFINITIONS = {
|
|
43
|
+
rss: {
|
|
44
|
+
type: "rss",
|
|
45
|
+
kind: "news",
|
|
46
|
+
label: "RSS / Atom feed",
|
|
47
|
+
settingsFields: [
|
|
48
|
+
{
|
|
49
|
+
key: "feedUrl",
|
|
50
|
+
label: "Feed URL",
|
|
51
|
+
input: "url",
|
|
52
|
+
required: true,
|
|
53
|
+
maxLength: 2048,
|
|
54
|
+
defaultValue: "https://g1.globo.com/dynamo/rss2.xml",
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
secretSettingKeys: [],
|
|
58
|
+
normalizeSettings: normalizeRssSettings,
|
|
59
|
+
},
|
|
60
|
+
"open-meteo": {
|
|
61
|
+
type: "open-meteo",
|
|
62
|
+
kind: "weather",
|
|
63
|
+
label: "Open-Meteo",
|
|
64
|
+
settingsFields: [],
|
|
65
|
+
secretSettingKeys: [],
|
|
66
|
+
normalizeSettings: noSettings,
|
|
67
|
+
},
|
|
68
|
+
frankfurter: {
|
|
69
|
+
type: "frankfurter",
|
|
70
|
+
kind: "currency",
|
|
71
|
+
label: "Frankfurter",
|
|
72
|
+
settingsFields: [],
|
|
73
|
+
secretSettingKeys: [],
|
|
74
|
+
normalizeSettings: noSettings,
|
|
75
|
+
},
|
|
76
|
+
"google-places": {
|
|
77
|
+
type: "google-places",
|
|
78
|
+
kind: "geo",
|
|
79
|
+
label: "Google Places",
|
|
80
|
+
settingsFields: [
|
|
81
|
+
{
|
|
82
|
+
key: "apiKey",
|
|
83
|
+
label: "API key",
|
|
84
|
+
input: "password",
|
|
85
|
+
required: true,
|
|
86
|
+
maxLength: 512,
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
secretSettingKeys: ["apiKey"],
|
|
90
|
+
normalizeSettings: normalizeGooglePlacesSettings,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
exports.WIDGET_SERVICE_PROVIDER_TYPES = Object.freeze(Object.keys(exports.WIDGET_SERVICE_PROVIDER_DEFINITIONS));
|
|
94
|
+
function normalizeWidgetServiceProviderSettings(type, settings) {
|
|
95
|
+
const definition = exports.WIDGET_SERVICE_PROVIDER_DEFINITIONS[type];
|
|
96
|
+
return definition.normalizeSettings(settings);
|
|
97
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget-services.types.spec.d.ts","sourceRoot":"","sources":["../src/widget-services.types.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const widget_services_types_1 = require("./widget-services.types");
|
|
4
|
+
describe("widget service contracts", () => {
|
|
5
|
+
it("uses a normalized result boundary for every provider", () => {
|
|
6
|
+
const news = { ok: false, error: "offline" };
|
|
7
|
+
const currency = {
|
|
8
|
+
ok: true,
|
|
9
|
+
value: { base: "USD", quote: "EUR", rate: 0.86, date: "2026-09-02" },
|
|
10
|
+
};
|
|
11
|
+
const weather = {};
|
|
12
|
+
expect(news.ok).toBe(false);
|
|
13
|
+
expect(currency.ok).toBe(true);
|
|
14
|
+
expect(weather).toEqual({});
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
describe("widget service provider definitions", () => {
|
|
18
|
+
it("registers every provider with its kind and settings schema", () => {
|
|
19
|
+
expect(widget_services_types_1.WIDGET_SERVICE_PROVIDER_TYPES).toEqual([
|
|
20
|
+
"rss",
|
|
21
|
+
"open-meteo",
|
|
22
|
+
"frankfurter",
|
|
23
|
+
"google-places",
|
|
24
|
+
]);
|
|
25
|
+
expect(widget_services_types_1.WIDGET_SERVICE_PROVIDER_DEFINITIONS["google-places"]).toMatchObject({
|
|
26
|
+
kind: "geo",
|
|
27
|
+
secretSettingKeys: ["apiKey"],
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
it("normalizes settings through the selected provider schema", () => {
|
|
31
|
+
expect((0, widget_services_types_1.normalizeWidgetServiceProviderSettings)("rss", {
|
|
32
|
+
feedUrl: " https://example.com/feed.xml ",
|
|
33
|
+
})).toEqual({ feedUrl: "https://example.com/feed.xml" });
|
|
34
|
+
expect((0, widget_services_types_1.normalizeWidgetServiceProviderSettings)("open-meteo", { ignored: true })).toEqual({});
|
|
35
|
+
});
|
|
36
|
+
it("rejects unsafe RSS URLs and masked secrets", () => {
|
|
37
|
+
expect(() => (0, widget_services_types_1.normalizeWidgetServiceProviderSettings)("rss", {
|
|
38
|
+
feedUrl: "https://localhost/feed",
|
|
39
|
+
})).toThrow("local or private host");
|
|
40
|
+
expect(() => (0, widget_services_types_1.normalizeWidgetServiceProviderSettings)("google-places", {
|
|
41
|
+
apiKey: widget_services_types_1.WIDGET_SERVICE_SECRET_MASK,
|
|
42
|
+
})).toThrow("API key is required");
|
|
43
|
+
});
|
|
44
|
+
});
|