Skip to content

Commit fcfa7f6

Browse files
committed
restrict DateTimePicker values to ISO Date strings
1 parent bf57fa7 commit fcfa7f6

File tree

8 files changed

+72
-5
lines changed

8 files changed

+72
-5
lines changed

packages/snaps-sdk/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,15 @@
9595
"@metamask/providers": "^22.1.1",
9696
"@metamask/rpc-errors": "^7.0.3",
9797
"@metamask/superstruct": "^3.2.1",
98-
"@metamask/utils": "^11.8.1"
98+
"@metamask/utils": "^11.8.1",
99+
"luxon": "^3.5.0"
99100
},
100101
"devDependencies": {
101102
"@lavamoat/allow-scripts": "^3.4.0",
102103
"@metamask/auto-changelog": "^5.0.2",
103104
"@ts-bridge/cli": "^0.6.1",
104105
"@types/jest": "^27.5.1",
106+
"@types/luxon": "^3",
105107
"deepmerge": "^4.2.2",
106108
"depcheck": "^1.4.7",
107109
"eslint": "^9.11.0",

packages/snaps-sdk/src/internals/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export type * from './helpers';
44
export * from './structs';
55
export * from './jsx';
66
export * from './svg';
7+
export * from './time';
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { create, is } from '@metamask/superstruct';
2+
import { DateTime } from 'luxon';
3+
4+
import { ISO8601DateStruct } from './time';
5+
6+
describe('ISO8601DateStruct', () => {
7+
it('should return true for a valid ISO 8601 date', () => {
8+
const value = DateTime.now().toISO();
9+
expect(is(value, ISO8601DateStruct)).toBe(true);
10+
});
11+
12+
it('should return false for an invalid ISO 8601 date', () => {
13+
const value = 'Mon Mar 31 2025';
14+
expect(is(value, ISO8601DateStruct)).toBe(false);
15+
});
16+
17+
it('should return false for an ISO 8601 date without timezone information', () => {
18+
const value = '2025-03-31T12:00:00';
19+
expect(is(value, ISO8601DateStruct)).toBe(false);
20+
});
21+
22+
it('should return an error message for invalid ISO 8601 date', () => {
23+
const value = 'Mon Mar 31 2025';
24+
expect(() => create(value, ISO8601DateStruct)).toThrow(
25+
'Not a valid ISO 8601 date',
26+
);
27+
});
28+
29+
it('should return an error message for ISO 8601 date without timezone information', () => {
30+
const value = '2025-03-31T12:00:00';
31+
expect(() => create(value, ISO8601DateStruct)).toThrow(
32+
'ISO 8601 date must have timezone information',
33+
);
34+
});
35+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { refine, string } from '@metamask/superstruct';
2+
import { DateTime } from 'luxon';
3+
4+
/**
5+
* Regex to match the offset part of an ISO 8601 date.
6+
*/
7+
const offsetRegex = /Z|([+-]\d{2}:?\d{2})$/u;
8+
9+
/**
10+
* Refines a string as an ISO 8601 date.
11+
*/
12+
export const ISO8601DateStruct = refine(string(), 'ISO 8601 date', (value) => {
13+
const parsedDate = DateTime.fromISO(value);
14+
15+
if (!parsedDate.isValid) {
16+
return 'Not a valid ISO 8601 date';
17+
}
18+
19+
if (!offsetRegex.test(value)) {
20+
// Luxon doesn't have a reliable way to check if timezone info was not provided
21+
return 'ISO 8601 date must have timezone information';
22+
}
23+
24+
return true;
25+
});

packages/snaps-sdk/src/jsx/components/form/DateTimePicker.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ describe('DateTimePicker', () => {
4747
type="datetime"
4848
placeholder="Select date and time"
4949
disabled={true}
50-
value="2024-01-01T12:00"
50+
value="2024-01-01T12:00Z"
5151
/>
5252
);
5353

@@ -58,7 +58,7 @@ describe('DateTimePicker', () => {
5858
type: 'datetime',
5959
placeholder: 'Select date and time',
6060
disabled: true,
61-
value: '2024-01-01T12:00',
61+
value: '2024-01-01T12:00Z',
6262
},
6363
key: null,
6464
});

packages/snaps-sdk/src/jsx/components/form/DateTimePicker.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import { createSnapComponent } from '../../component';
55
*
66
* @property name - The name of the date/time picker field. This is used to identify the
77
* date/time picker field in the form data.
8-
* @property value - The value of the date/time picker field.
8+
* @property value - The value of the date/time picker field. Must be an ISO 8601 date string with
9+
* timezone information.
910
* @property type - The type of the date/time picker field. Can be 'date', 'time', or 'datetime'.
1011
* Defaults to 'datetime'.
1112
* @property placeholder - The placeholder text of the date/time picker field.

packages/snaps-sdk/src/jsx/validation.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ import {
9090
selectiveUnion,
9191
svg,
9292
typedUnion,
93+
ISO8601DateStruct,
9394
} from '../internals';
9495
import {
9596
NonEip155AssetTypeStruct,
@@ -393,7 +394,7 @@ export const DateTimePickerStruct: Describe<DateTimePickerElement> = element(
393394
),
394395
placeholder: optional(string()),
395396
disabled: optional(boolean()),
396-
value: optional(string()),
397+
value: optional(ISO8601DateStruct),
397398
},
398399
);
399400

yarn.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4518,6 +4518,7 @@ __metadata:
45184518
"@metamask/utils": "npm:^11.8.1"
45194519
"@ts-bridge/cli": "npm:^0.6.1"
45204520
"@types/jest": "npm:^27.5.1"
4521+
"@types/luxon": "npm:^3"
45214522
deepmerge: "npm:^4.2.2"
45224523
depcheck: "npm:^1.4.7"
45234524
eslint: "npm:^9.11.0"
@@ -4526,6 +4527,7 @@ __metadata:
45264527
jest-fetch-mock: "npm:^3.0.3"
45274528
jest-it-up: "npm:^2.0.0"
45284529
jest-silent-reporter: "npm:^0.6.0"
4530+
luxon: "npm:^3.5.0"
45294531
prettier: "npm:^3.3.3"
45304532
ts-jest: "npm:^29.1.1"
45314533
typescript: "npm:~5.3.3"

0 commit comments

Comments
 (0)