@asgard-js/react 0.0.43 → 0.0.44-canary.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/components/chatbot/chatbot.d.ts +1 -1
- package/dist/components/chatbot/chatbot.d.ts.map +1 -1
- package/dist/context/asgard-service-context.d.ts +1 -1
- package/dist/context/asgard-service-context.d.ts.map +1 -1
- package/dist/hooks/use-channel.d.ts +1 -1
- package/dist/hooks/use-channel.d.ts.map +1 -1
- package/dist/index.js +20457 -20222
- package/package.json +3 -3
- package/.babelrc +0 -12
- package/eslint.config.cjs +0 -12
- package/src/components/.DS_Store +0 -0
- package/src/components/chatbot/api-key-input/api-key-input.module.scss +0 -156
- package/src/components/chatbot/api-key-input/api-key-input.tsx +0 -111
- package/src/components/chatbot/api-key-input/index.ts +0 -1
- package/src/components/chatbot/chatbot-body/chatbot-body.module.scss +0 -13
- package/src/components/chatbot/chatbot-body/chatbot-body.tsx +0 -45
- package/src/components/chatbot/chatbot-body/conversation-message-renderer.tsx +0 -55
- package/src/components/chatbot/chatbot-body/index.ts +0 -1
- package/src/components/chatbot/chatbot-container/chatbot-container.module.scss +0 -41
- package/src/components/chatbot/chatbot-container/chatbot-container.tsx +0 -49
- package/src/components/chatbot/chatbot-container/chatbot-full-screen-container.tsx +0 -54
- package/src/components/chatbot/chatbot-footer/chatbot-footer.module.scss +0 -67
- package/src/components/chatbot/chatbot-footer/chatbot-footer.tsx +0 -140
- package/src/components/chatbot/chatbot-footer/index.ts +0 -1
- package/src/components/chatbot/chatbot-footer/speech-input-button.tsx +0 -132
- package/src/components/chatbot/chatbot-header/chatbot-header.module.scss +0 -48
- package/src/components/chatbot/chatbot-header/chatbot-header.tsx +0 -98
- package/src/components/chatbot/chatbot-header/index.ts +0 -1
- package/src/components/chatbot/chatbot.module.scss +0 -24
- package/src/components/chatbot/chatbot.spec.tsx +0 -8
- package/src/components/chatbot/chatbot.tsx +0 -227
- package/src/components/chatbot/profile-icon.tsx +0 -26
- package/src/components/index.ts +0 -2
- package/src/components/templates/avatar/avatar.module.scss +0 -6
- package/src/components/templates/avatar/avatar.tsx +0 -28
- package/src/components/templates/avatar/index.ts +0 -1
- package/src/components/templates/button-template/button-template.module.scss +0 -0
- package/src/components/templates/button-template/button-template.tsx +0 -45
- package/src/components/templates/button-template/card.module.scss +0 -58
- package/src/components/templates/button-template/card.spec.tsx +0 -213
- package/src/components/templates/button-template/card.tsx +0 -123
- package/src/components/templates/button-template/index.ts +0 -1
- package/src/components/templates/carousel-template/carousel-template.module.scss +0 -15
- package/src/components/templates/carousel-template/carousel-template.tsx +0 -49
- package/src/components/templates/carousel-template/index.ts +0 -1
- package/src/components/templates/chart-template/chart-template.module.scss +0 -52
- package/src/components/templates/chart-template/chart-template.tsx +0 -75
- package/src/components/templates/chart-template/index.ts +0 -1
- package/src/components/templates/hint-template/hint-template.module.scss +0 -43
- package/src/components/templates/hint-template/hint-template.tsx +0 -76
- package/src/components/templates/hint-template/index.ts +0 -1
- package/src/components/templates/image-template/image-template.module.scss +0 -67
- package/src/components/templates/image-template/image-template.tsx +0 -58
- package/src/components/templates/image-template/index.ts +0 -1
- package/src/components/templates/index.ts +0 -10
- package/src/components/templates/quick-replies/index.ts +0 -1
- package/src/components/templates/quick-replies/quick-replies.module.scss +0 -16
- package/src/components/templates/quick-replies/quick-replies.tsx +0 -47
- package/src/components/templates/template-box/index.ts +0 -2
- package/src/components/templates/template-box/template-box-content.module.scss +0 -13
- package/src/components/templates/template-box/template-box-content.tsx +0 -30
- package/src/components/templates/template-box/template-box.module.scss +0 -19
- package/src/components/templates/template-box/template-box.tsx +0 -48
- package/src/components/templates/text-template/bot-typing-box.tsx +0 -81
- package/src/components/templates/text-template/bot-typing-placeholder.tsx +0 -28
- package/src/components/templates/text-template/index.ts +0 -3
- package/src/components/templates/text-template/text-template.module.scss +0 -131
- package/src/components/templates/text-template/text-template.tsx +0 -94
- package/src/components/templates/text-template/use-react-markdown-renderer.spec.tsx +0 -758
- package/src/components/templates/time/index.ts +0 -1
- package/src/components/templates/time/time.module.scss +0 -6
- package/src/components/templates/time/time.tsx +0 -34
- package/src/context/asgard-app-initialization-context.tsx +0 -154
- package/src/context/asgard-service-context.tsx +0 -148
- package/src/context/asgard-template-context.tsx +0 -83
- package/src/context/asgard-theme-context.tsx +0 -553
- package/src/context/index.ts +0 -4
- package/src/hooks/index.ts +0 -11
- package/src/hooks/use-asgard-service-client.ts +0 -68
- package/src/hooks/use-channel.ts +0 -160
- package/src/hooks/use-debounce.ts +0 -18
- package/src/hooks/use-deep-compare-memo.ts +0 -19
- package/src/hooks/use-is-on-screen-keyboard-open.ts +0 -43
- package/src/hooks/use-on-screen-keyboard-scroll-fix.ts +0 -17
- package/src/hooks/use-prevent-over-scrolling.ts +0 -77
- package/src/hooks/use-react-markdown-renderer.tsx +0 -278
- package/src/hooks/use-resize-observer.tsx +0 -27
- package/src/hooks/use-update-vh.ts +0 -30
- package/src/hooks/use-viewport-size.ts +0 -51
- package/src/icons/add_a_photo.svg +0 -3
- package/src/icons/bot.svg +0 -14
- package/src/icons/close.svg +0 -3
- package/src/icons/distance.svg +0 -3
- package/src/icons/eye-off.svg +0 -4
- package/src/icons/eye.svg +0 -4
- package/src/icons/mic.svg +0 -3
- package/src/icons/photo_library.svg +0 -3
- package/src/icons/profile.svg +0 -28
- package/src/icons/refresh.svg +0 -3
- package/src/icons/send.svg +0 -3
- package/src/icons/stop.svg +0 -22
- package/src/icons/volume_up.svg +0 -3
- package/src/index.ts +0 -4
- package/src/models/bot-provider.ts +0 -108
- package/src/styles/_index.scss +0 -1
- package/src/styles/_styles.scss +0 -11
- package/src/styles/colors/_colors.scss +0 -10
- package/src/styles/colors/_index.scss +0 -1
- package/src/styles/colors/_variables.scss +0 -72
- package/src/styles/palette/_index.scss +0 -1
- package/src/styles/palette/_palette.scss +0 -42
- package/src/styles/palette/_variables.scss +0 -40
- package/src/styles/radius/_index.scss +0 -1
- package/src/styles/radius/_radius.scss +0 -8
- package/src/styles/radius/_variables.scss +0 -12
- package/src/styles/spacing/_index.scss +0 -1
- package/src/styles/spacing/_spacing.scss +0 -8
- package/src/styles/spacing/_variables.scss +0 -13
- package/src/styles/utils/_index.scss +0 -1
- package/src/styles/utils/_map.scss +0 -22
- package/src/test-setup.ts +0 -1
- package/src/utils/color-utils.ts +0 -52
- package/src/utils/deep-merge.ts +0 -26
- package/src/utils/extractors.ts +0 -20
- package/src/utils/format-time.ts +0 -8
- package/src/utils/index.ts +0 -1
- package/src/utils/is.ts +0 -72
- package/src/utils/selectors.ts +0 -7
- package/src/utils/uri-validation.spec.ts +0 -208
- package/src/utils/uri-validation.ts +0 -103
- package/tsconfig.json +0 -16
- package/tsconfig.lib.json +0 -63
- package/tsconfig.spec.json +0 -36
- package/tsconfig.tsbuildinfo +0 -1
- package/vite.config.ts +0 -63
- /package/dist/{style.css → index.css} +0 -0
package/src/utils/extractors.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export function extractRefs(obj: unknown): unknown[] {
|
|
2
|
-
const refs: unknown[] = [];
|
|
3
|
-
|
|
4
|
-
function traverse(o: unknown): void {
|
|
5
|
-
if (o && typeof o === 'object') {
|
|
6
|
-
if (Array.isArray(o)) {
|
|
7
|
-
for (const item of o) traverse(item);
|
|
8
|
-
} else {
|
|
9
|
-
for (const key of Object.keys(o))
|
|
10
|
-
traverse((o as Record<string, unknown>)[key]);
|
|
11
|
-
}
|
|
12
|
-
} else {
|
|
13
|
-
refs.push(o);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
traverse(obj);
|
|
18
|
-
|
|
19
|
-
return refs;
|
|
20
|
-
}
|
package/src/utils/format-time.ts
DELETED
package/src/utils/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './format-time';
|
package/src/utils/is.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Performs a deep equality check between two values (primitive, array, plain object).
|
|
3
|
-
* - Functions are compared by reference.
|
|
4
|
-
* - No circular reference support.
|
|
5
|
-
* - Returns true if deeply equal, false otherwise.
|
|
6
|
-
*/
|
|
7
|
-
export function isEqual(a: unknown, b: unknown): boolean {
|
|
8
|
-
// Check for strict equality first (handles primitives and reference equality)
|
|
9
|
-
if (a === b) {
|
|
10
|
-
return true;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// Handle null cases
|
|
14
|
-
if (a === null || b === null) {
|
|
15
|
-
return a === b;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Compare types
|
|
19
|
-
if (typeof a !== typeof b) {
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Handle arrays
|
|
24
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
25
|
-
if (a.length !== b.length) {
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
for (let i = 0; i < a.length; i++) {
|
|
30
|
-
if (!isEqual(a[i], b[i])) {
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// If one is array and the other is not
|
|
39
|
-
if (Array.isArray(a) || Array.isArray(b)) {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Handle objects (but not functions)
|
|
44
|
-
if (typeof a === 'object' && typeof b === 'object') {
|
|
45
|
-
const aKeys = Object.keys(a as object);
|
|
46
|
-
const bKeys = Object.keys(b as object);
|
|
47
|
-
if (aKeys.length !== bKeys.length) {
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
for (const key of aKeys) {
|
|
52
|
-
// Use Object.prototype.hasOwnProperty.call to be safe
|
|
53
|
-
if (!Object.prototype.hasOwnProperty.call(b, key)) {
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (
|
|
58
|
-
!isEqual(
|
|
59
|
-
(a as Record<string, unknown>)[key],
|
|
60
|
-
(b as Record<string, unknown>)[key]
|
|
61
|
-
)
|
|
62
|
-
) {
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Fallback for functions, symbols, etc. (compare by reference)
|
|
71
|
-
return a === b;
|
|
72
|
-
}
|
package/src/utils/selectors.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { BotProviderMetadataResponse } from '../models/bot-provider';
|
|
2
|
-
|
|
3
|
-
export const annotationSelectorFromBotProviderMetadata = (
|
|
4
|
-
value: BotProviderMetadataResponse
|
|
5
|
-
): Record<string, unknown> => {
|
|
6
|
-
return JSON.parse(value.annotations['asgard-ai.com/additional-annotation']);
|
|
7
|
-
};
|
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { isValidUri, safeWindowOpen } from './uri-validation';
|
|
3
|
-
|
|
4
|
-
describe('URI Validation', () => {
|
|
5
|
-
let consoleWarnSpy: ReturnType<typeof vi.spyOn>;
|
|
6
|
-
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
|
|
7
|
-
let windowOpenSpy: ReturnType<typeof vi.spyOn>;
|
|
8
|
-
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
|
|
11
|
-
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
|
|
12
|
-
windowOpenSpy = vi.spyOn(window, 'open').mockImplementation(() => null);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
consoleWarnSpy.mockRestore();
|
|
17
|
-
consoleErrorSpy.mockRestore();
|
|
18
|
-
windowOpenSpy.mockRestore();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
describe('isValidUri', () => {
|
|
22
|
-
describe('should return false for null/undefined/empty values', () => {
|
|
23
|
-
it('should reject null', () => {
|
|
24
|
-
expect(isValidUri(null)).toBe(false);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('should reject undefined', () => {
|
|
28
|
-
expect(isValidUri(undefined)).toBe(false);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('should reject empty string', () => {
|
|
32
|
-
expect(isValidUri('')).toBe(false);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('should reject whitespace-only string', () => {
|
|
36
|
-
expect(isValidUri(' ')).toBe(false);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
describe('should accept safe protocols', () => {
|
|
41
|
-
it('should accept http URLs', () => {
|
|
42
|
-
expect(isValidUri('http://example.com')).toBe(true);
|
|
43
|
-
expect(isValidUri('HTTP://EXAMPLE.COM')).toBe(true);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('should accept https URLs', () => {
|
|
47
|
-
expect(isValidUri('https://example.com')).toBe(true);
|
|
48
|
-
expect(isValidUri('HTTPS://EXAMPLE.COM')).toBe(true);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('should accept mailto URLs', () => {
|
|
52
|
-
expect(isValidUri('mailto:test@example.com')).toBe(true);
|
|
53
|
-
expect(isValidUri('MAILTO:test@example.com')).toBe(true);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('should accept tel URLs', () => {
|
|
57
|
-
expect(isValidUri('tel:+1234567890')).toBe(true);
|
|
58
|
-
expect(isValidUri('TEL:+1234567890')).toBe(true);
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
describe('should reject dangerous protocols', () => {
|
|
63
|
-
it('should reject javascript URLs', () => {
|
|
64
|
-
// eslint-disable-next-line no-script-url
|
|
65
|
-
const jsUri = 'javascript:alert("xss")';
|
|
66
|
-
// eslint-disable-next-line no-script-url
|
|
67
|
-
const jsUriUpper = 'JAVASCRIPT:alert("xss")';
|
|
68
|
-
expect(isValidUri(jsUri)).toBe(false);
|
|
69
|
-
expect(isValidUri(jsUriUpper)).toBe(false);
|
|
70
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
71
|
-
expect.stringContaining('Blocked unknown protocol: javascript:')
|
|
72
|
-
);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should reject data URLs', () => {
|
|
76
|
-
expect(isValidUri('data:text/html,<script>alert("xss")</script>')).toBe(false);
|
|
77
|
-
expect(isValidUri('DATA:text/html,<script>alert("xss")</script>')).toBe(false);
|
|
78
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
79
|
-
expect.stringContaining('Blocked dangerous protocol: data:')
|
|
80
|
-
);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('should reject file URLs', () => {
|
|
84
|
-
expect(isValidUri('file:///etc/passwd')).toBe(false);
|
|
85
|
-
expect(isValidUri('FILE:///etc/passwd')).toBe(false);
|
|
86
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
87
|
-
expect.stringContaining('Blocked dangerous protocol: file:')
|
|
88
|
-
);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('should reject ftp URLs', () => {
|
|
92
|
-
expect(isValidUri('ftp://example.com')).toBe(false);
|
|
93
|
-
expect(isValidUri('FTP://example.com')).toBe(false);
|
|
94
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
95
|
-
expect.stringContaining('Blocked dangerous protocol: ftp:')
|
|
96
|
-
);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('should reject browser extension protocols', () => {
|
|
100
|
-
expect(isValidUri('chrome-extension://example')).toBe(false);
|
|
101
|
-
expect(isValidUri('moz-extension://example')).toBe(false);
|
|
102
|
-
expect(isValidUri('safari-extension://example')).toBe(false);
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
describe('should handle relative URLs', () => {
|
|
107
|
-
it('should accept absolute paths', () => {
|
|
108
|
-
expect(isValidUri('/path/to/resource')).toBe(true);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('should accept relative paths', () => {
|
|
112
|
-
expect(isValidUri('./relative/path')).toBe(true);
|
|
113
|
-
expect(isValidUri('../parent/path')).toBe(true);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('should accept protocol-less domains', () => {
|
|
117
|
-
expect(isValidUri('example.com')).toBe(true);
|
|
118
|
-
expect(isValidUri('subdomain.example.com')).toBe(true);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
describe('should handle edge cases', () => {
|
|
123
|
-
it('should reject malformed URLs with unknown protocols', () => {
|
|
124
|
-
expect(isValidUri('unknown-protocol://example')).toBe(false);
|
|
125
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
126
|
-
expect.stringContaining('Blocked unknown protocol: unknown-protocol:')
|
|
127
|
-
);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it('should handle URLs with special characters', () => {
|
|
131
|
-
expect(isValidUri('https://example.com/path?param=value&other=123')).toBe(true);
|
|
132
|
-
expect(isValidUri('https://example.com/path#fragment')).toBe(true);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('should handle internationalized domain names', () => {
|
|
136
|
-
expect(isValidUri('https://例え.テスト')).toBe(true);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it('should reject completely malformed strings', () => {
|
|
140
|
-
expect(isValidUri('not-a-url-at-all')).toBe(true); // Treated as domain
|
|
141
|
-
expect(isValidUri('::invalid::')).toBe(false);
|
|
142
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
143
|
-
expect.stringContaining('Invalid URI format'),
|
|
144
|
-
expect.any(Error)
|
|
145
|
-
);
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
describe('should handle protocol edge cases', () => {
|
|
150
|
-
it('should handle URLs with ports', () => {
|
|
151
|
-
expect(isValidUri('http://example.com:8080')).toBe(true);
|
|
152
|
-
expect(isValidUri('https://example.com:443')).toBe(true);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('should handle URLs with authentication', () => {
|
|
156
|
-
expect(isValidUri('https://user:pass@example.com')).toBe(true);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it('should handle protocol-relative URLs', () => {
|
|
160
|
-
expect(isValidUri('//example.com')).toBe(true); // URL constructor treats this as valid
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
describe('safeWindowOpen', () => {
|
|
166
|
-
it('should open valid URLs', () => {
|
|
167
|
-
const mockWindow = {} as Window;
|
|
168
|
-
windowOpenSpy.mockReturnValue(mockWindow);
|
|
169
|
-
|
|
170
|
-
const result = safeWindowOpen('https://example.com', '_blank');
|
|
171
|
-
|
|
172
|
-
expect(windowOpenSpy).toHaveBeenCalledWith('https://example.com', '_blank', undefined);
|
|
173
|
-
expect(result).toBe(mockWindow);
|
|
174
|
-
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it('should block invalid URLs and return null', () => {
|
|
178
|
-
// eslint-disable-next-line no-script-url
|
|
179
|
-
const maliciousUri = 'javascript:alert("xss")';
|
|
180
|
-
const result = safeWindowOpen(maliciousUri, '_blank');
|
|
181
|
-
|
|
182
|
-
expect(windowOpenSpy).not.toHaveBeenCalled();
|
|
183
|
-
expect(result).toBe(null);
|
|
184
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
185
|
-
`Blocked attempt to open unsafe URI: ${maliciousUri}`
|
|
186
|
-
);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it('should pass through all window.open parameters', () => {
|
|
190
|
-
const mockWindow = {} as Window;
|
|
191
|
-
windowOpenSpy.mockReturnValue(mockWindow);
|
|
192
|
-
|
|
193
|
-
safeWindowOpen('https://example.com', '_blank', 'width=800,height=600');
|
|
194
|
-
|
|
195
|
-
expect(windowOpenSpy).toHaveBeenCalledWith(
|
|
196
|
-
'https://example.com',
|
|
197
|
-
'_blank',
|
|
198
|
-
'width=800,height=600'
|
|
199
|
-
);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('should handle null/undefined URIs', () => {
|
|
203
|
-
expect(safeWindowOpen(null)).toBe(null);
|
|
204
|
-
expect(safeWindowOpen(undefined)).toBe(null);
|
|
205
|
-
expect(windowOpenSpy).not.toHaveBeenCalled();
|
|
206
|
-
});
|
|
207
|
-
});
|
|
208
|
-
});
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* URI validation utilities for preventing XSS attacks via malicious URIs
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Safe protocols that are allowed for external links
|
|
7
|
-
*/
|
|
8
|
-
const SAFE_PROTOCOLS = ['http:', 'https:', 'mailto:', 'tel:'] as const;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Dangerous protocols that should be blocked to prevent XSS attacks
|
|
12
|
-
*/
|
|
13
|
-
const DANGEROUS_PROTOCOLS = [
|
|
14
|
-
'data:',
|
|
15
|
-
'file:',
|
|
16
|
-
'ftp:',
|
|
17
|
-
'about:',
|
|
18
|
-
'chrome:',
|
|
19
|
-
'chrome-extension:',
|
|
20
|
-
'moz-extension:',
|
|
21
|
-
'safari-extension:',
|
|
22
|
-
'ms-browser-extension:',
|
|
23
|
-
'vbscript:',
|
|
24
|
-
] as const;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Validates if a URI is safe to open with window.open()
|
|
28
|
-
*
|
|
29
|
-
* @param uri - The URI to validate
|
|
30
|
-
* @returns true if the URI is safe to open, false otherwise
|
|
31
|
-
*/
|
|
32
|
-
export function isValidUri(uri: string | null | undefined): boolean {
|
|
33
|
-
// Handle null, undefined, or empty strings
|
|
34
|
-
if (!uri || typeof uri !== 'string' || uri.trim() === '') {
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const trimmedUri = uri.trim();
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
// Try to parse as URL to validate structure
|
|
42
|
-
const url = new URL(trimmedUri);
|
|
43
|
-
|
|
44
|
-
// Check if protocol is in the safe list
|
|
45
|
-
const protocol = url.protocol.toLowerCase();
|
|
46
|
-
|
|
47
|
-
if (SAFE_PROTOCOLS.includes(protocol as (typeof SAFE_PROTOCOLS)[number])) {
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Log blocked dangerous protocols for debugging
|
|
52
|
-
if (DANGEROUS_PROTOCOLS.includes(protocol as (typeof DANGEROUS_PROTOCOLS)[number])) {
|
|
53
|
-
// eslint-disable-next-line no-console
|
|
54
|
-
console.warn(`Blocked dangerous protocol: ${protocol} in URI: ${trimmedUri}`);
|
|
55
|
-
} else {
|
|
56
|
-
// eslint-disable-next-line no-console
|
|
57
|
-
console.warn(`Blocked unknown protocol: ${protocol} in URI: ${trimmedUri}`);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return false;
|
|
61
|
-
} catch (error) {
|
|
62
|
-
// If URL parsing fails, try to handle relative URLs or special cases
|
|
63
|
-
if (trimmedUri.startsWith('/') || trimmedUri.startsWith('./') || trimmedUri.startsWith('../')) {
|
|
64
|
-
// Relative URLs are generally safe
|
|
65
|
-
return true;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Check for protocol-less URLs (e.g., "example.com")
|
|
69
|
-
if (!trimmedUri.includes(':') && !trimmedUri.startsWith('//')) {
|
|
70
|
-
// Looks like a domain without protocol, relatively safe
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Log invalid URIs for debugging
|
|
75
|
-
// eslint-disable-next-line no-console
|
|
76
|
-
console.warn(`Invalid URI format: ${trimmedUri}`, error);
|
|
77
|
-
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Safely opens a URI after validation
|
|
84
|
-
*
|
|
85
|
-
* @param uri - The URI to open
|
|
86
|
-
* @param target - The window target (same as window.open target parameter)
|
|
87
|
-
* @param features - Window features (same as window.open features parameter)
|
|
88
|
-
* @returns The opened window reference or null if URI was blocked
|
|
89
|
-
*/
|
|
90
|
-
export function safeWindowOpen(
|
|
91
|
-
uri: string | null | undefined,
|
|
92
|
-
target?: string,
|
|
93
|
-
features?: string
|
|
94
|
-
): Window | null {
|
|
95
|
-
if (!isValidUri(uri)) {
|
|
96
|
-
// eslint-disable-next-line no-console
|
|
97
|
-
console.error(`Blocked attempt to open unsafe URI: ${uri}`);
|
|
98
|
-
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return window.open(uri as string, target, features);
|
|
103
|
-
}
|
package/tsconfig.json
DELETED
package/tsconfig.lib.json
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.base.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"baseUrl": ".",
|
|
5
|
-
"paths": {
|
|
6
|
-
"src/*": ["src/*"],
|
|
7
|
-
"@asgard-js/core": ["../core/src/index.ts"]
|
|
8
|
-
},
|
|
9
|
-
"outDir": "out-tsc/react",
|
|
10
|
-
"types": [
|
|
11
|
-
"node",
|
|
12
|
-
"@nx/react/typings/cssmodule.d.ts",
|
|
13
|
-
"@nx/react/typings/image.d.ts",
|
|
14
|
-
"@types/dom-speech-recognition",
|
|
15
|
-
"vite-plugin-svgr/client",
|
|
16
|
-
"vite/client"
|
|
17
|
-
],
|
|
18
|
-
"jsx": "react-jsx",
|
|
19
|
-
"module": "esnext",
|
|
20
|
-
"moduleResolution": "bundler",
|
|
21
|
-
"tsBuildInfoFile": "out-tsc/react/tsconfig.lib.tsbuildinfo"
|
|
22
|
-
},
|
|
23
|
-
"exclude": [
|
|
24
|
-
"out-tsc",
|
|
25
|
-
"dist",
|
|
26
|
-
"**/*.spec.ts",
|
|
27
|
-
"**/*.test.ts",
|
|
28
|
-
"**/*.spec.tsx",
|
|
29
|
-
"**/*.test.tsx",
|
|
30
|
-
"**/*.spec.js",
|
|
31
|
-
"**/*.test.js",
|
|
32
|
-
"**/*.spec.jsx",
|
|
33
|
-
"**/*.test.jsx",
|
|
34
|
-
"vite.config.ts",
|
|
35
|
-
"vite.config.mts",
|
|
36
|
-
"vitest.config.ts",
|
|
37
|
-
"vitest.config.mts",
|
|
38
|
-
"src/**/*.test.ts",
|
|
39
|
-
"src/**/*.spec.ts",
|
|
40
|
-
"src/**/*.test.tsx",
|
|
41
|
-
"src/**/*.spec.tsx",
|
|
42
|
-
"src/**/*.test.js",
|
|
43
|
-
"src/**/*.spec.js",
|
|
44
|
-
"src/**/*.test.jsx",
|
|
45
|
-
"src/**/*.spec.jsx",
|
|
46
|
-
"eslint.config.js",
|
|
47
|
-
"eslint.config.cjs",
|
|
48
|
-
"eslint.config.mjs",
|
|
49
|
-
"../../packages/core/dist"
|
|
50
|
-
],
|
|
51
|
-
"include": [
|
|
52
|
-
"src/**/*.js",
|
|
53
|
-
"src/**/*.jsx",
|
|
54
|
-
"src/**/*.ts",
|
|
55
|
-
"src/**/*.tsx",
|
|
56
|
-
"../../packages/core/**/*.ts"
|
|
57
|
-
],
|
|
58
|
-
"references": [
|
|
59
|
-
{
|
|
60
|
-
"path": "../core/tsconfig.lib.json"
|
|
61
|
-
}
|
|
62
|
-
]
|
|
63
|
-
}
|
package/tsconfig.spec.json
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.base.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"outDir": "./out-tsc/vitest",
|
|
5
|
-
"types": [
|
|
6
|
-
"vitest/globals",
|
|
7
|
-
"vitest/importMeta",
|
|
8
|
-
"vite/client",
|
|
9
|
-
"node",
|
|
10
|
-
"vitest"
|
|
11
|
-
],
|
|
12
|
-
"jsx": "react-jsx",
|
|
13
|
-
"module": "esnext",
|
|
14
|
-
"moduleResolution": "bundler"
|
|
15
|
-
},
|
|
16
|
-
"include": [
|
|
17
|
-
"vite.config.ts",
|
|
18
|
-
"vite.config.mts",
|
|
19
|
-
"vitest.config.ts",
|
|
20
|
-
"vitest.config.mts",
|
|
21
|
-
"src/**/*.test.ts",
|
|
22
|
-
"src/**/*.spec.ts",
|
|
23
|
-
"src/**/*.test.tsx",
|
|
24
|
-
"src/**/*.spec.tsx",
|
|
25
|
-
"src/**/*.test.js",
|
|
26
|
-
"src/**/*.spec.js",
|
|
27
|
-
"src/**/*.test.jsx",
|
|
28
|
-
"src/**/*.spec.jsx",
|
|
29
|
-
"src/**/*.d.ts"
|
|
30
|
-
],
|
|
31
|
-
"references": [
|
|
32
|
-
{
|
|
33
|
-
"path": "./tsconfig.lib.json"
|
|
34
|
-
}
|
|
35
|
-
]
|
|
36
|
-
}
|
package/tsconfig.tsbuildinfo
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"fileNames":[],"fileInfos":[],"root":[],"options":{"allowJs":false,"allowSyntheticDefaultImports":true,"composite":true,"declaration":true,"declarationMap":true,"emitDeclarationOnly":true,"emitDecoratorMetadata":false,"esModuleInterop":true,"experimentalDecorators":false,"importHelpers":true,"module":99,"noEmitOnError":true,"noFallthroughCasesInSwitch":true,"noImplicitOverride":true,"noImplicitReturns":true,"noUnusedLocals":true,"removeComments":false,"rootDir":"../..","skipDefaultLibCheck":false,"skipLibCheck":true,"sourceMap":false,"strict":true,"target":99,"verbatimModuleSyntax":false},"version":"5.6.3"}
|
package/vite.config.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/// <reference types='vitest' />
|
|
2
|
-
import { defineConfig } from 'vite';
|
|
3
|
-
import react from '@vitejs/plugin-react';
|
|
4
|
-
import dts from 'vite-plugin-dts';
|
|
5
|
-
import svgr from 'vite-plugin-svgr';
|
|
6
|
-
import * as path from 'path';
|
|
7
|
-
|
|
8
|
-
export default defineConfig({
|
|
9
|
-
root: __dirname,
|
|
10
|
-
cacheDir: '../../node_modules/.vite/packages/react',
|
|
11
|
-
resolve: {
|
|
12
|
-
alias: {
|
|
13
|
-
src: path.resolve(__dirname, 'src'),
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
plugins: [
|
|
17
|
-
react(),
|
|
18
|
-
svgr(),
|
|
19
|
-
dts({
|
|
20
|
-
entryRoot: 'src',
|
|
21
|
-
tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'),
|
|
22
|
-
}),
|
|
23
|
-
],
|
|
24
|
-
// Uncomment this if you are using workers.
|
|
25
|
-
// worker: {
|
|
26
|
-
// plugins: [ nxViteTsPaths() ],
|
|
27
|
-
// },
|
|
28
|
-
// Configuration for building your library.
|
|
29
|
-
// See: https://vitejs.dev/guide/build.html#library-mode
|
|
30
|
-
build: {
|
|
31
|
-
outDir: './dist',
|
|
32
|
-
emptyOutDir: true,
|
|
33
|
-
reportCompressedSize: true,
|
|
34
|
-
commonjsOptions: {
|
|
35
|
-
transformMixedEsModules: true,
|
|
36
|
-
},
|
|
37
|
-
lib: {
|
|
38
|
-
// Could also be a dictionary or array of multiple entry points.
|
|
39
|
-
entry: 'src/index.ts',
|
|
40
|
-
name: '@asgard-js/react',
|
|
41
|
-
fileName: 'index',
|
|
42
|
-
// Change this to the formats you want to support.
|
|
43
|
-
// Don't forget to update your package.json as well.
|
|
44
|
-
formats: ['es'],
|
|
45
|
-
},
|
|
46
|
-
rollupOptions: {
|
|
47
|
-
// External packages that should not be bundled into your library.
|
|
48
|
-
external: ['react', 'react-dom', 'react/jsx-runtime', '@asgard-js/core'],
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
test: {
|
|
52
|
-
watch: false,
|
|
53
|
-
globals: true,
|
|
54
|
-
environment: 'jsdom',
|
|
55
|
-
setupFiles: ['src/test-setup.ts'],
|
|
56
|
-
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
|
57
|
-
reporters: ['default'],
|
|
58
|
-
coverage: {
|
|
59
|
-
reportsDirectory: './test-output/vitest/coverage',
|
|
60
|
-
provider: 'v8',
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
});
|
|
File without changes
|