botframework-webchat 4.13.0 → 4.15.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/.eslintrc.yml +5 -104
- package/.prettierrc.yml +1 -1
- package/README.md +33 -36
- package/babel.config.json +2 -2
- package/babel.sanitize-html.config.json +10 -0
- package/lib/AddFullBundle.d.ts +30 -0
- package/lib/AddFullBundle.d.ts.map +1 -0
- package/lib/AddFullBundle.js +76 -0
- package/lib/FullComposer.d.ts +8 -0
- package/lib/FullComposer.d.ts.map +1 -0
- package/lib/FullComposer.js +6 -16
- package/lib/FullReactWebChat.d.ts +8 -0
- package/lib/FullReactWebChat.d.ts.map +1 -0
- package/lib/FullReactWebChat.js +8 -23
- package/lib/adaptiveCards/AdaptiveCardsComposer.d.ts +10 -0
- package/lib/adaptiveCards/AdaptiveCardsComposer.d.ts.map +1 -0
- package/lib/adaptiveCards/AdaptiveCardsComposer.js +6 -2
- package/lib/adaptiveCards/AdaptiveCardsContext.d.ts +8 -0
- package/lib/adaptiveCards/AdaptiveCardsContext.d.ts.map +1 -0
- package/lib/adaptiveCards/AdaptiveCardsContext.js +3 -3
- package/lib/adaptiveCards/AdaptiveCardsStyleOptions.d.ts +5 -3
- package/lib/adaptiveCards/AdaptiveCardsStyleOptions.d.ts.map +1 -1
- package/lib/adaptiveCards/AdaptiveCardsStyleSet.d.ts +7 -0
- package/lib/adaptiveCards/AdaptiveCardsStyleSet.d.ts.map +1 -0
- package/lib/{FullBundleStyleOptions.js → adaptiveCards/AdaptiveCardsStyleSet.js} +0 -0
- package/lib/adaptiveCards/Attachment/AdaptiveCardAttachment.d.ts +9 -0
- package/lib/adaptiveCards/Attachment/AdaptiveCardAttachment.d.ts.map +1 -0
- package/lib/adaptiveCards/Attachment/AdaptiveCardAttachment.js +1 -1
- package/lib/adaptiveCards/Attachment/AdaptiveCardBuilder.d.ts +28 -0
- package/lib/adaptiveCards/Attachment/AdaptiveCardBuilder.d.ts.map +1 -0
- package/lib/adaptiveCards/Attachment/AdaptiveCardBuilder.js +13 -6
- package/lib/adaptiveCards/Attachment/AdaptiveCardContent.d.ts +9 -0
- package/lib/adaptiveCards/Attachment/AdaptiveCardContent.d.ts.map +1 -0
- package/lib/adaptiveCards/Attachment/AdaptiveCardContent.js +8 -4
- package/lib/adaptiveCards/Attachment/AdaptiveCardRenderer.d.ts +12 -0
- package/lib/adaptiveCards/Attachment/AdaptiveCardRenderer.d.ts.map +1 -0
- package/lib/adaptiveCards/Attachment/AdaptiveCardRenderer.js +310 -85
- package/lib/adaptiveCards/Attachment/AnimationCardContent.d.ts +10 -0
- package/lib/adaptiveCards/Attachment/AnimationCardContent.d.ts.map +1 -0
- package/lib/adaptiveCards/Attachment/AnimationCardContent.js +1 -1
- package/lib/adaptiveCards/Attachment/AudioCardContent.d.ts +10 -0
- package/lib/adaptiveCards/Attachment/AudioCardContent.d.ts.map +1 -0
- package/lib/adaptiveCards/Attachment/AudioCardContent.js +1 -1
- package/lib/adaptiveCards/Attachment/CommonCard.js +11 -6
- package/lib/adaptiveCards/Attachment/HeroCardContent.d.ts +10 -0
- package/lib/adaptiveCards/Attachment/HeroCardContent.d.ts.map +1 -0
- package/lib/adaptiveCards/Attachment/HeroCardContent.js +11 -6
- package/lib/adaptiveCards/Attachment/OAuthCardContent.d.ts +10 -0
- package/lib/adaptiveCards/Attachment/OAuthCardContent.d.ts.map +1 -0
- package/lib/adaptiveCards/Attachment/OAuthCardContent.js +11 -6
- package/lib/adaptiveCards/Attachment/ReceiptCardContent.d.ts +10 -0
- package/lib/adaptiveCards/Attachment/ReceiptCardContent.d.ts.map +1 -0
- package/lib/adaptiveCards/Attachment/ReceiptCardContent.js +11 -6
- package/lib/adaptiveCards/Attachment/SignInCardContent.d.ts +10 -0
- package/lib/adaptiveCards/Attachment/SignInCardContent.d.ts.map +1 -0
- package/lib/adaptiveCards/Attachment/SignInCardContent.js +1 -1
- package/lib/adaptiveCards/Attachment/ThumbnailCardContent.d.ts +10 -0
- package/lib/adaptiveCards/Attachment/ThumbnailCardContent.d.ts.map +1 -0
- package/lib/adaptiveCards/Attachment/ThumbnailCardContent.js +11 -6
- package/lib/adaptiveCards/Attachment/VideoCardContent.d.ts +20 -0
- package/lib/adaptiveCards/Attachment/VideoCardContent.d.ts.map +1 -0
- package/lib/adaptiveCards/Attachment/VideoCardContent.js +7 -5
- package/lib/adaptiveCards/AttachmentForScreenReader/AdaptiveCardAttachment.js +7 -3
- package/lib/adaptiveCards/AttachmentForScreenReader/RichCardAttachment.js +1 -1
- package/lib/adaptiveCards/Styles/StyleSet/AdaptiveCardRenderer.d.ts +59 -0
- package/lib/adaptiveCards/Styles/StyleSet/AdaptiveCardRenderer.d.ts.map +1 -0
- package/lib/adaptiveCards/Styles/StyleSet/AdaptiveCardRenderer.js +8 -1
- package/lib/adaptiveCards/Styles/adaptiveCardHostConfig.d.ts +126 -0
- package/lib/adaptiveCards/Styles/adaptiveCardHostConfig.d.ts.map +1 -0
- package/lib/adaptiveCards/Styles/adaptiveCardHostConfig.js +29 -9
- package/lib/adaptiveCards/Styles/createAdaptiveCardsStyleSet.d.ts +5 -0
- package/lib/adaptiveCards/Styles/createAdaptiveCardsStyleSet.d.ts.map +1 -0
- package/lib/adaptiveCards/Styles/createAdaptiveCardsStyleSet.js +2 -2
- package/lib/adaptiveCards/createAdaptiveCardsAttachmentForScreenReaderMiddleware.d.ts +3 -0
- package/lib/adaptiveCards/createAdaptiveCardsAttachmentForScreenReaderMiddleware.d.ts.map +1 -0
- package/lib/adaptiveCards/createAdaptiveCardsAttachmentForScreenReaderMiddleware.js +5 -11
- package/lib/adaptiveCards/createAdaptiveCardsAttachmentMiddleware.d.ts +3 -0
- package/lib/adaptiveCards/createAdaptiveCardsAttachmentMiddleware.d.ts.map +1 -0
- package/lib/adaptiveCards/createAdaptiveCardsAttachmentMiddleware.js +2 -15
- package/lib/adaptiveCards/defaultStyleOptions.d.ts +4 -0
- package/lib/adaptiveCards/defaultStyleOptions.d.ts.map +1 -0
- package/lib/adaptiveCards/defaultStyleOptions.js +3 -2
- package/lib/adaptiveCards/hooks/internal/useAdaptiveCardsContext.d.ts +3 -0
- package/lib/adaptiveCards/hooks/internal/useAdaptiveCardsContext.d.ts.map +1 -0
- package/lib/adaptiveCards/hooks/internal/useAdaptiveCardsContext.js +1 -1
- package/lib/adaptiveCards/hooks/internal/useParseAdaptiveCardJSON.d.ts +2 -0
- package/lib/adaptiveCards/hooks/internal/useParseAdaptiveCardJSON.d.ts.map +1 -0
- package/lib/adaptiveCards/hooks/internal/useParseAdaptiveCardJSON.js +7 -5
- package/lib/adaptiveCards/hooks/internal/useUniqueId.js +1 -1
- package/lib/adaptiveCards/hooks/useAdaptiveCardsHostConfig.d.ts +2 -0
- package/lib/adaptiveCards/hooks/useAdaptiveCardsHostConfig.d.ts.map +1 -0
- package/lib/adaptiveCards/hooks/useAdaptiveCardsHostConfig.js +3 -5
- package/lib/adaptiveCards/hooks/useAdaptiveCardsPackage.d.ts +3 -0
- package/lib/adaptiveCards/hooks/useAdaptiveCardsPackage.d.ts.map +1 -0
- package/lib/adaptiveCards/hooks/useAdaptiveCardsPackage.js +1 -1
- package/lib/adaptiveCards/normalizeStyleOptions.d.ts +3 -0
- package/lib/adaptiveCards/normalizeStyleOptions.d.ts.map +1 -0
- package/lib/adaptiveCards/normalizeStyleOptions.js +1 -1
- package/lib/addVersion.js +2 -2
- package/lib/createBrowserWebSpeechPonyfillFactory.d.ts +3 -0
- package/lib/createBrowserWebSpeechPonyfillFactory.d.ts.map +1 -0
- package/lib/createBrowserWebSpeechPonyfillFactory.js +7 -4
- package/lib/createCognitiveServicesSpeechServicesPonyfillFactory.d.ts +17 -0
- package/lib/createCognitiveServicesSpeechServicesPonyfillFactory.d.ts.map +1 -0
- package/lib/createCognitiveServicesSpeechServicesPonyfillFactory.js +17 -11
- package/lib/createDirectLine.d.ts +17 -0
- package/lib/createDirectLine.d.ts.map +1 -0
- package/lib/createDirectLine.js +3 -2
- package/lib/createDirectLineAppServiceExtension.d.ts +7 -0
- package/lib/createDirectLineAppServiceExtension.d.ts.map +1 -0
- package/lib/createDirectLineAppServiceExtension.js +1 -1
- package/lib/createDirectLineSpeechAdapters.d.ts +25 -0
- package/lib/createDirectLineSpeechAdapters.d.ts.map +1 -0
- package/lib/createDirectLineSpeechAdapters.js +57 -3
- package/lib/createFullStyleSet.d.ts +1375 -0
- package/lib/createFullStyleSet.d.ts.map +1 -0
- package/lib/createFullStyleSet.js +2 -2
- package/lib/fullBundleDefaultStyleOptions.js +4 -4
- package/lib/hooks/useStyleOptions.d.ts +3 -0
- package/lib/hooks/useStyleOptions.d.ts.map +1 -0
- package/lib/hooks/useStyleOptions.js +21 -0
- package/lib/index-es5.d.ts +4 -20
- package/lib/index-es5.d.ts.map +1 -1
- package/lib/index-es5.js +3 -43
- package/lib/index-minimal.d.ts +9 -6
- package/lib/index-minimal.d.ts.map +1 -1
- package/lib/index-minimal.js +27 -21
- package/lib/index.d.ts +164 -18
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +19 -12
- package/lib/polyfill.d.ts +23 -0
- package/lib/polyfill.d.ts.map +1 -0
- package/lib/polyfill.js +46 -0
- package/lib/renderMarkdown.d.ts +6 -0
- package/lib/renderMarkdown.d.ts.map +1 -0
- package/lib/renderMarkdown.js +36 -5
- package/lib/renderWebChat.d.ts +3 -0
- package/lib/renderWebChat.d.ts.map +1 -0
- package/lib/renderWebChat.js +1 -1
- package/lib/speech/CustomAudioInputStream.d.ts +94 -0
- package/lib/speech/CustomAudioInputStream.d.ts.map +1 -0
- package/lib/speech/CustomAudioInputStream.js +441 -0
- package/lib/speech/bytesPerSample.d.ts +2 -0
- package/lib/speech/bytesPerSample.d.ts.map +1 -0
- package/lib/speech/bytesPerSample.js +12 -0
- package/lib/speech/createAudioConfig.d.ts +23 -0
- package/lib/speech/createAudioConfig.d.ts.map +1 -0
- package/lib/speech/createAudioConfig.js +90 -0
- package/lib/speech/createAudioContext.d.ts +3 -0
- package/lib/speech/createAudioContext.d.ts.map +1 -0
- package/lib/speech/createAudioContext.js +22 -0
- package/lib/speech/createMicrophoneAudioConfigAndAudioContext.d.ts +9 -0
- package/lib/speech/createMicrophoneAudioConfigAndAudioContext.d.ts.map +1 -0
- package/lib/speech/createMicrophoneAudioConfigAndAudioContext.js +161 -0
- package/lib/speech/getUserMedia.d.ts +2 -0
- package/lib/speech/getUserMedia.d.ts.map +1 -0
- package/lib/speech/getUserMedia.js +25 -0
- package/lib/types/AdaptiveCardsPackage.d.ts +13 -0
- package/lib/types/AdaptiveCardsPackage.d.ts.map +1 -0
- package/lib/types/AdaptiveCardsPackage.js +2 -0
- package/lib/types/CognitiveServicesAudioOutputFormat.d.ts +3 -0
- package/lib/types/CognitiveServicesAudioOutputFormat.d.ts.map +1 -0
- package/lib/types/CognitiveServicesAudioOutputFormat.js +2 -0
- package/lib/types/CognitiveServicesCredentials.d.ts +18 -0
- package/lib/types/CognitiveServicesCredentials.d.ts.map +1 -0
- package/lib/types/CognitiveServicesCredentials.js +2 -0
- package/lib/types/CognitiveServicesTextNormalization.d.ts +3 -0
- package/lib/types/CognitiveServicesTextNormalization.d.ts.map +1 -0
- package/lib/types/CognitiveServicesTextNormalization.js +2 -0
- package/lib/{FullBundleStyleOptions.d.ts → types/FullBundleStyleOptions.d.ts} +1 -1
- package/lib/types/FullBundleStyleOptions.d.ts.map +1 -0
- package/lib/types/FullBundleStyleOptions.js +2 -0
- package/lib/useComposerProps.d.ts +22 -0
- package/lib/useComposerProps.d.ts.map +1 -0
- package/lib/useComposerProps.js +6 -8
- package/package.json +46 -48
- package/src/AddFullBundle.tsx +93 -0
- package/src/FullComposer.tsx +38 -0
- package/src/FullReactWebChat.tsx +32 -0
- package/src/__tests__/createDirectLine.spec.js +2 -0
- package/src/__tests__/renderMarkdown.spec.js +1 -1
- package/src/__tests__/versionTag.es5.spec.js +3 -0
- package/src/__tests__/versionTag.full.spec.js +3 -0
- package/src/__tests__/versionTag.minimal.spec.js +3 -0
- package/src/adaptiveCards/{AdaptiveCardsComposer.js → AdaptiveCardsComposer.tsx} +17 -5
- package/src/adaptiveCards/AdaptiveCardsContext.ts +12 -0
- package/src/adaptiveCards/AdaptiveCardsStyleOptions.ts +5 -3
- package/src/adaptiveCards/AdaptiveCardsStyleSet.ts +7 -0
- package/src/adaptiveCards/Attachment/{AdaptiveCardAttachment.js → AdaptiveCardAttachment.tsx} +8 -2
- package/src/adaptiveCards/Attachment/AdaptiveCardBuilder.ts +27 -17
- package/src/adaptiveCards/Attachment/{AdaptiveCardContent.js → AdaptiveCardContent.tsx} +8 -2
- package/src/adaptiveCards/Attachment/AdaptiveCardRenderer.tsx +768 -0
- package/src/adaptiveCards/Attachment/{AnimationCardContent.js → AnimationCardContent.tsx} +9 -2
- package/src/adaptiveCards/Attachment/{AudioCardContent.js → AudioCardContent.tsx} +9 -2
- package/src/adaptiveCards/Attachment/CommonCard.js +2 -1
- package/src/adaptiveCards/Attachment/{HeroCardContent.js → HeroCardContent.tsx} +12 -3
- package/src/adaptiveCards/Attachment/{OAuthCardContent.js → OAuthCardContent.tsx} +11 -3
- package/src/adaptiveCards/Attachment/{ReceiptCardContent.js → ReceiptCardContent.tsx} +58 -28
- package/src/adaptiveCards/Attachment/{SignInCardContent.js → SignInCardContent.tsx} +9 -2
- package/src/adaptiveCards/Attachment/{ThumbnailCardContent.js → ThumbnailCardContent.tsx} +12 -3
- package/src/adaptiveCards/Attachment/{VideoCardContent.js → VideoCardContent.tsx} +15 -3
- package/src/adaptiveCards/Styles/StyleSet/AdaptiveCardRenderer.ts +9 -1
- package/src/adaptiveCards/Styles/adaptiveCardHostConfig.ts +28 -8
- package/src/adaptiveCards/Styles/createAdaptiveCardsStyleSet.spec.js +2 -0
- package/src/adaptiveCards/Styles/createAdaptiveCardsStyleSet.ts +7 -4
- package/src/adaptiveCards/{createAdaptiveCardsAttachmentForScreenReaderMiddleware.js → createAdaptiveCardsAttachmentForScreenReaderMiddleware.tsx} +16 -15
- package/src/adaptiveCards/createAdaptiveCardsAttachmentMiddleware.tsx +44 -0
- package/src/adaptiveCards/defaultStyleOptions.ts +2 -1
- package/src/adaptiveCards/hooks/internal/{useAdaptiveCardsContext.js → useAdaptiveCardsContext.ts} +1 -1
- package/src/adaptiveCards/hooks/internal/{useParseAdaptiveCardJSON.js → useParseAdaptiveCardJSON.ts} +5 -2
- package/src/adaptiveCards/hooks/internal/{useUniqueId.js → useUniqueId.ts} +1 -1
- package/src/adaptiveCards/hooks/{useAdaptiveCardsHostConfig.js → useAdaptiveCardsHostConfig.ts} +6 -8
- package/src/adaptiveCards/hooks/{useAdaptiveCardsPackage.js → useAdaptiveCardsPackage.ts} +2 -1
- package/src/createBrowserWebSpeechPonyfillFactory.ts +21 -0
- package/src/createCognitiveServicesSpeechServicesPonyfillFactory.spec.js +36 -6
- package/src/createCognitiveServicesSpeechServicesPonyfillFactory.ts +78 -0
- package/src/createDirectLine.ts +65 -0
- package/src/createDirectLineAppServiceExtension.ts +22 -0
- package/src/createDirectLineSpeechAdapters.ts +84 -0
- package/src/createFullStyleSet.ts +2 -2
- package/src/fullBundleDefaultStyleOptions.ts +3 -3
- package/src/hooks/useStyleOptions.ts +9 -0
- package/src/index-es5.ts +3 -28
- package/src/index-minimal.ts +3 -2
- package/src/index.ts +22 -10
- package/src/polyfill.ts +29 -0
- package/src/renderMarkdown.ts +140 -0
- package/src/renderWebChat.tsx +6 -0
- package/src/speech/CustomAudioInputStream.ts +356 -0
- package/src/speech/bytesPerSample.ts +4 -0
- package/src/speech/createAudioConfig.spec.js +23 -0
- package/src/speech/createAudioConfig.ts +77 -0
- package/src/speech/createAudioContext.ts +16 -0
- package/src/speech/createMicrophoneAudioConfigAndAudioContext.ts +146 -0
- package/src/speech/getUserMedia.ts +14 -0
- package/src/tsconfig.json +1 -1
- package/src/types/AdaptiveCardsPackage.ts +23 -0
- package/src/types/CognitiveServicesAudioOutputFormat.ts +28 -0
- package/src/types/CognitiveServicesCredentials.ts +28 -0
- package/src/types/CognitiveServicesTextNormalization.ts +3 -0
- package/src/{FullBundleStyleOptions.ts → types/FullBundleStyleOptions.ts} +1 -1
- package/src/{useComposerProps.js → useComposerProps.ts} +21 -11
- package/webpack.config.js +46 -6
- package/.eslintignore +0 -9
- package/lib/FullBundleStyleOptions.d.ts.map +0 -1
- package/src/FullComposer.js +0 -40
- package/src/FullReactWebChat.js +0 -47
- package/src/adaptiveCards/AdaptiveCardsContext.js +0 -5
- package/src/adaptiveCards/Attachment/AdaptiveCardRenderer.js +0 -492
- package/src/adaptiveCards/createAdaptiveCardsAttachmentMiddleware.js +0 -41
- package/src/createBrowserWebSpeechPonyfillFactory.js +0 -16
- package/src/createCognitiveServicesSpeechServicesPonyfillFactory.js +0 -58
- package/src/createDirectLine.js +0 -38
- package/src/createDirectLineAppServiceExtension.js +0 -12
- package/src/createDirectLineSpeechAdapters.js +0 -5
- package/src/index-es5.tsx +0 -49
- package/src/index-minimal.tsx +0 -9
- package/src/index.tsx +0 -45
- package/src/renderMarkdown.js +0 -97
- package/src/renderWebChat.js +0 -6
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/* eslint no-magic-numbers: ["error", { "ignore": [0, 1, 2] }] */
|
|
2
|
+
|
|
3
|
+
import iterator from 'markdown-it-for-inline';
|
|
4
|
+
import MarkdownIt from 'markdown-it';
|
|
5
|
+
import markdownItAttrs from 'markdown-it-attrs-es5';
|
|
6
|
+
import sanitizeHTML from 'sanitize-html';
|
|
7
|
+
|
|
8
|
+
const SANITIZE_HTML_OPTIONS = {
|
|
9
|
+
allowedAttributes: {
|
|
10
|
+
a: ['aria-label', 'href', 'name', 'rel', 'target', 'title'],
|
|
11
|
+
img: ['alt', 'class', 'src']
|
|
12
|
+
},
|
|
13
|
+
allowedSchemes: ['data', 'http', 'https', 'ftp', 'mailto', 'sip', 'tel'],
|
|
14
|
+
allowedTags: [
|
|
15
|
+
'a',
|
|
16
|
+
'b',
|
|
17
|
+
'blockquote',
|
|
18
|
+
'br',
|
|
19
|
+
'caption',
|
|
20
|
+
'code',
|
|
21
|
+
'del',
|
|
22
|
+
'div',
|
|
23
|
+
'em',
|
|
24
|
+
'h1',
|
|
25
|
+
'h2',
|
|
26
|
+
'h3',
|
|
27
|
+
'h4',
|
|
28
|
+
'h5',
|
|
29
|
+
'h6',
|
|
30
|
+
'hr',
|
|
31
|
+
'i',
|
|
32
|
+
'img',
|
|
33
|
+
'ins',
|
|
34
|
+
'li',
|
|
35
|
+
'nl',
|
|
36
|
+
'ol',
|
|
37
|
+
'p',
|
|
38
|
+
'pre',
|
|
39
|
+
's',
|
|
40
|
+
'span',
|
|
41
|
+
'strike',
|
|
42
|
+
'strong',
|
|
43
|
+
'table',
|
|
44
|
+
'tbody',
|
|
45
|
+
'td',
|
|
46
|
+
'tfoot',
|
|
47
|
+
'th',
|
|
48
|
+
'thead',
|
|
49
|
+
'tr',
|
|
50
|
+
'ul'
|
|
51
|
+
]
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Put a transparent pixel instead of the "open in new window" icon, so developers can easily modify the icon in CSS.
|
|
55
|
+
const TRANSPARENT_GIF = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
|
56
|
+
|
|
57
|
+
// This is used for parsing Markdown for external links.
|
|
58
|
+
const internalMarkdownIt = new MarkdownIt();
|
|
59
|
+
|
|
60
|
+
const MARKDOWN_ATTRS_LEFT_DELIMITER = '⟬';
|
|
61
|
+
// Make sure the delimiter is free from any RegExp characters, such as *, ?, etc.
|
|
62
|
+
// IE11 does not support "u" flag and Babel could not remove it. We intentionally omitting the "u" flag here.
|
|
63
|
+
// eslint-disable-next-line security/detect-non-literal-regexp, require-unicode-regexp
|
|
64
|
+
const MARKDOWN_ATTRS_LEFT_DELIMITER_PATTERN = new RegExp(MARKDOWN_ATTRS_LEFT_DELIMITER, 'g');
|
|
65
|
+
|
|
66
|
+
const MARKDOWN_ATTRS_RIGHT_DELIMITER = '⟭';
|
|
67
|
+
// Make sure the delimiter is free from any RegExp characters, such as *, ?, etc.
|
|
68
|
+
// IE11 does not support "u" flag and Babel could not remove it. We intentionally omitting the "u" flag here.
|
|
69
|
+
// eslint-disable-next-line security/detect-non-literal-regexp, require-unicode-regexp
|
|
70
|
+
const MARKDOWN_ATTRS_RIGHT_DELIMITER_PATTERN = new RegExp(MARKDOWN_ATTRS_RIGHT_DELIMITER, 'g');
|
|
71
|
+
|
|
72
|
+
export default function render(
|
|
73
|
+
markdown: string,
|
|
74
|
+
{ markdownRespectCRLF }: { markdownRespectCRLF: boolean },
|
|
75
|
+
{ externalLinkAlt = '' }: { externalLinkAlt?: string } = {}
|
|
76
|
+
): string {
|
|
77
|
+
if (markdownRespectCRLF) {
|
|
78
|
+
markdown = markdown.replace(/\n\r|\r\n/gu, carriageReturn => (carriageReturn === '\n\r' ? '\r\n' : '\n\r'));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Related to #3165.
|
|
82
|
+
// We only support attributes "aria-label" and should leave other attributes as-is.
|
|
83
|
+
// However, `markdown-it-attrs` remove unrecognized attributes, such as {hello}.
|
|
84
|
+
// Before passing to `markdown-it-attrs`, we will convert known attributes from {aria-label="..."} into ⟬aria-label="..."⟭ (using white tortoise shell brackets).
|
|
85
|
+
// Then, we ask `markdown-it-attrs` to only process the new brackets, so it should only try to process things that we allowlisted.
|
|
86
|
+
// Lastly, we revert tortoise shell brackets back to curly brackets, for unprocessed attributes.
|
|
87
|
+
markdown = markdown
|
|
88
|
+
.replace(/\{\s*aria-label()\s*\}/giu, `${MARKDOWN_ATTRS_LEFT_DELIMITER}aria-label${MARKDOWN_ATTRS_RIGHT_DELIMITER}`)
|
|
89
|
+
.replace(
|
|
90
|
+
/\{\s*aria-label=("[^"]*"|[^\s}]*)\s*\}/giu,
|
|
91
|
+
(_, valueInsideQuotes) =>
|
|
92
|
+
`${MARKDOWN_ATTRS_LEFT_DELIMITER}aria-label=${valueInsideQuotes}${MARKDOWN_ATTRS_RIGHT_DELIMITER}`
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
let html = new MarkdownIt({
|
|
96
|
+
breaks: false,
|
|
97
|
+
html: false,
|
|
98
|
+
linkify: true,
|
|
99
|
+
typographer: true,
|
|
100
|
+
xhtmlOut: true
|
|
101
|
+
})
|
|
102
|
+
.use(markdownItAttrs, {
|
|
103
|
+
// `markdown-it-attrs` is added for accessibility and allow bot developers to specify `aria-label`.
|
|
104
|
+
// We are allowlisting `aria-label` only as it is allowlisted in `sanitize-html`.
|
|
105
|
+
// Other `aria-*` will be sanitized even we allowlisted here.
|
|
106
|
+
allowedAttributes: ['aria-label'],
|
|
107
|
+
leftDelimiter: MARKDOWN_ATTRS_LEFT_DELIMITER,
|
|
108
|
+
rightDelimiter: MARKDOWN_ATTRS_RIGHT_DELIMITER
|
|
109
|
+
})
|
|
110
|
+
.use(iterator, 'url_new_win', 'link_open', (tokens, index) => {
|
|
111
|
+
const token = tokens[+index];
|
|
112
|
+
|
|
113
|
+
token.attrSet('rel', 'noopener noreferrer');
|
|
114
|
+
token.attrSet('target', '_blank');
|
|
115
|
+
|
|
116
|
+
const linkOpenToken = tokens.find(({ type }) => type === 'link_open');
|
|
117
|
+
const [, href] = linkOpenToken.attrs.find(([name]) => name === 'href');
|
|
118
|
+
|
|
119
|
+
// Adds a new icon if the link is http: or https:.
|
|
120
|
+
// Don't add if it's a phone number, etc.
|
|
121
|
+
if (/^https?:/iu.test(href)) {
|
|
122
|
+
externalLinkAlt && token.attrSet('title', externalLinkAlt);
|
|
123
|
+
|
|
124
|
+
const iconTokens = internalMarkdownIt.parseInline(``)[0].children;
|
|
125
|
+
|
|
126
|
+
iconTokens[0].attrJoin('class', 'webchat__markdown__external-link-icon');
|
|
127
|
+
|
|
128
|
+
tokens.splice(index + 2, 0, ...iconTokens);
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
.render(markdown);
|
|
132
|
+
|
|
133
|
+
// Restore attributes not processed by `markdown-it-attrs`.
|
|
134
|
+
// TODO: [P2] #2511 After we fixed our polyfill story, we should use "String.prototype.replaceAll" instead of RegExp for replace all occurrences.
|
|
135
|
+
html = html.replace(MARKDOWN_ATTRS_LEFT_DELIMITER_PATTERN, '{').replace(MARKDOWN_ATTRS_RIGHT_DELIMITER_PATTERN, '}');
|
|
136
|
+
|
|
137
|
+
// The signature from "sanitize-html" module is not correct.
|
|
138
|
+
// @ts-ignore
|
|
139
|
+
return sanitizeHTML(html, SANITIZE_HTML_OPTIONS);
|
|
140
|
+
}
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { AudioInputStream } from 'microsoft-cognitiveservices-speech-sdk';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
AudioSourceErrorEvent,
|
|
5
|
+
AudioSourceEvent,
|
|
6
|
+
AudioSourceInitializingEvent,
|
|
7
|
+
AudioSourceOffEvent,
|
|
8
|
+
AudioSourceReadyEvent,
|
|
9
|
+
AudioStreamNodeAttachedEvent,
|
|
10
|
+
AudioStreamNodeAttachingEvent,
|
|
11
|
+
AudioStreamNodeDetachedEvent,
|
|
12
|
+
AudioStreamNodeErrorEvent,
|
|
13
|
+
Events,
|
|
14
|
+
EventSource
|
|
15
|
+
} from 'microsoft-cognitiveservices-speech-sdk/distrib/lib/src/common/Exports';
|
|
16
|
+
|
|
17
|
+
import { AudioStreamFormatImpl } from 'microsoft-cognitiveservices-speech-sdk/distrib/lib/src/sdk/Audio/AudioStreamFormat';
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
connectivity as Connectivity,
|
|
21
|
+
ISpeechConfigAudioDevice,
|
|
22
|
+
type as Type
|
|
23
|
+
} from 'microsoft-cognitiveservices-speech-sdk/distrib/lib/src/common.speech/Exports';
|
|
24
|
+
|
|
25
|
+
import { isForbiddenPropertyName } from 'botframework-webchat-core';
|
|
26
|
+
import { v4 } from 'uuid';
|
|
27
|
+
import createDeferred, { DeferredPromise } from 'p-defer-es5';
|
|
28
|
+
|
|
29
|
+
type AudioStreamNode = {
|
|
30
|
+
detach: () => Promise<void>;
|
|
31
|
+
id: () => string;
|
|
32
|
+
read: () => Promise<StreamChunk<ArrayBuffer>>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type DeviceInfo = {
|
|
36
|
+
connectivity?: Connectivity | 'Bluetooth' | 'Wired' | 'WiFi' | 'Cellular' | 'InBuilt' | 'Unknown';
|
|
37
|
+
manufacturer?: string;
|
|
38
|
+
model?: string;
|
|
39
|
+
type?:
|
|
40
|
+
| Type
|
|
41
|
+
| 'Phone'
|
|
42
|
+
| 'Speaker'
|
|
43
|
+
| 'Car'
|
|
44
|
+
| 'Headset'
|
|
45
|
+
| 'Thermostat'
|
|
46
|
+
| 'Microphones'
|
|
47
|
+
| 'Deskphone'
|
|
48
|
+
| 'RemoteControl'
|
|
49
|
+
| 'Unknown'
|
|
50
|
+
| 'File'
|
|
51
|
+
| 'Stream';
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type Format = {
|
|
55
|
+
bitsPerSample: number;
|
|
56
|
+
channels: number;
|
|
57
|
+
samplesPerSec: number;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
type NormalizedOptions = Required<Omit<Options, 'debug'>> & {
|
|
61
|
+
debug: boolean;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
type Options = {
|
|
65
|
+
debug?: true;
|
|
66
|
+
id?: string;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
type StreamChunk<T> = {
|
|
70
|
+
isEnd: boolean;
|
|
71
|
+
buffer: T;
|
|
72
|
+
timeReceived: number;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const SYMBOL_DEVICE_INFO_DEFERRED = Symbol('deviceInfoDeferred');
|
|
76
|
+
const SYMBOL_EVENTS = Symbol('events');
|
|
77
|
+
const SYMBOL_FORMAT_DEFERRED = Symbol('formatDeferred');
|
|
78
|
+
const SYMBOL_OPTIONS = Symbol('options');
|
|
79
|
+
|
|
80
|
+
// Speech SDK quirks: Only 2 lifecycle functions are actually used.
|
|
81
|
+
// They are: attach() and turnOff().
|
|
82
|
+
// Others are not used, including: blob(), close(), detach(), turnOn().
|
|
83
|
+
abstract class CustomAudioInputStream extends AudioInputStream {
|
|
84
|
+
constructor(options: Options = {}) {
|
|
85
|
+
super();
|
|
86
|
+
|
|
87
|
+
const normalizedOptions: NormalizedOptions = {
|
|
88
|
+
debug: options.debug || false,
|
|
89
|
+
id: options.id || v4().replace(/-/gu, '')
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// False alarm: indexer is a constant of type Symbol.
|
|
93
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
94
|
+
this[SYMBOL_DEVICE_INFO_DEFERRED] = createDeferred<DeviceInfo>();
|
|
95
|
+
|
|
96
|
+
// False alarm: indexer is a constant of type Symbol.
|
|
97
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
98
|
+
this[SYMBOL_EVENTS] = new EventSource<AudioSourceEvent>();
|
|
99
|
+
|
|
100
|
+
// False alarm: indexer is a constant of type Symbol.
|
|
101
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
102
|
+
this[SYMBOL_FORMAT_DEFERRED] = createDeferred<AudioStreamFormatImpl>();
|
|
103
|
+
|
|
104
|
+
// False alarm: indexer is a constant of type Symbol.
|
|
105
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
106
|
+
this[SYMBOL_OPTIONS] = normalizedOptions;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
[SYMBOL_DEVICE_INFO_DEFERRED]: DeferredPromise<DeviceInfo>;
|
|
110
|
+
[SYMBOL_EVENTS]: EventSource<AudioSourceEvent>;
|
|
111
|
+
[SYMBOL_FORMAT_DEFERRED]: DeferredPromise<AudioStreamFormatImpl>;
|
|
112
|
+
[SYMBOL_OPTIONS]: NormalizedOptions;
|
|
113
|
+
|
|
114
|
+
/** Gets the event source for listening to events. */
|
|
115
|
+
// ESLint: This code will only works in browsers other than IE11. Only works in ES5 is okay.
|
|
116
|
+
// @ts-ignore Accessors are only available when targeting ECMAScript 5 and higher.ts(1056)
|
|
117
|
+
get events(): EventSource<AudioSourceEvent> {
|
|
118
|
+
// False alarm: indexer is a constant of type Symbol.
|
|
119
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
120
|
+
return this[SYMBOL_EVENTS];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Gets the format of the audio stream. */
|
|
124
|
+
// Speech SDK quirks: `AudioStreamFormatImpl` is internal implementation while `AudioStreamFormat` is public.
|
|
125
|
+
// It is weird to expose `AudioStreamFormatImpl` instead of `AudioStreamFormat`.
|
|
126
|
+
// Speech SDK quirks: It is weird to return a `Promise` in a property.
|
|
127
|
+
// Speech SDK quirks: In normal speech recognition, getter of "format" is called only after "attach".
|
|
128
|
+
// But in Direct Line Speech, it is called before "attach".
|
|
129
|
+
// ESLint: This code will only works in browsers other than IE11. Only works in ES5 is okay.
|
|
130
|
+
// @ts-ignore Accessors are only available when targeting ECMAScript 5 and higher.ts(1056)
|
|
131
|
+
get format(): Promise<AudioStreamFormatImpl> {
|
|
132
|
+
this.debug('Getting "format".');
|
|
133
|
+
|
|
134
|
+
// False alarm: indexer is a constant of type Symbol.
|
|
135
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
136
|
+
return this[SYMBOL_FORMAT_DEFERRED].promise;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Gets the ID of this audio stream. */
|
|
140
|
+
id(): string {
|
|
141
|
+
// False alarm: indexer is a constant of type Symbol.
|
|
142
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
143
|
+
return this[SYMBOL_OPTIONS].id;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Emits an event. */
|
|
147
|
+
// Speech SDK quirks: In JavaScript, onXxx means "listen to event XXX".
|
|
148
|
+
// Instead, in Speech SDK, it means "emit event XXX".
|
|
149
|
+
protected onEvent(event: AudioSourceEvent): void {
|
|
150
|
+
// False alarm: indexer is a constant of type Symbol.
|
|
151
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
152
|
+
this[SYMBOL_EVENTS].onEvent(event);
|
|
153
|
+
Events.instance.onEvent(event);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Emits an `AudioSourceInitializingEvent`. */
|
|
157
|
+
protected emitInitializing(): void {
|
|
158
|
+
this.debug('Emitting "AudioSourceInitializingEvent".');
|
|
159
|
+
this.onEvent(new AudioSourceInitializingEvent(this.id()));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Emits an `AudioSourceReadyEvent`. */
|
|
163
|
+
protected emitReady(): void {
|
|
164
|
+
this.debug('Emitting "AudioSourceReadyEvent".');
|
|
165
|
+
this.onEvent(new AudioSourceReadyEvent(this.id()));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Emits an `AudioSourceErrorEvent`. */
|
|
169
|
+
// Speech SDK quirks: Since "turnOn" is never called and "turnOff" does not work in Direct Line Speech, the "source error" event is not emitted at all.
|
|
170
|
+
// Instead, we only emit "node error" event.
|
|
171
|
+
protected emitError(error: Error): void {
|
|
172
|
+
this.debug('Emitting "AudioSourceErrorEvent".', { error });
|
|
173
|
+
|
|
174
|
+
// Speech SDK quirks: "error" is a string, instead of object of type "Error".
|
|
175
|
+
this.onEvent(new AudioSourceErrorEvent(this.id(), error.message));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Emits an `AudioStreamNodeAttachingEvent`. */
|
|
179
|
+
protected emitNodeAttaching(audioNodeId: string): void {
|
|
180
|
+
this.debug(`Emitting "AudioStreamNodeAttachingEvent" for node "${audioNodeId}".`);
|
|
181
|
+
this.onEvent(new AudioStreamNodeAttachingEvent(this.id(), audioNodeId));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Emits an `AudioStreamNodeAttachedEvent`. */
|
|
185
|
+
protected emitNodeAttached(audioNodeId: string): void {
|
|
186
|
+
this.debug(`Emitting "AudioStreamNodeAttachedEvent" for node "${audioNodeId}".`);
|
|
187
|
+
this.onEvent(new AudioStreamNodeAttachedEvent(this.id(), audioNodeId));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Emits an `AudioStreamNodeErrorEvent`. */
|
|
191
|
+
protected emitNodeError(audioNodeId: string, error: Error): void {
|
|
192
|
+
this.debug(`Emitting "AudioStreamNodeErrorEvent" for node "${audioNodeId}".`, { error });
|
|
193
|
+
|
|
194
|
+
// Speech SDK quirks: "error" is a string, instead of object of type "Error".
|
|
195
|
+
this.onEvent(new AudioStreamNodeErrorEvent(this.id(), audioNodeId, error.message));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Emits an `AudioStreamNodeDetachedEvent`. */
|
|
199
|
+
protected emitNodeDetached(audioNodeId: string): void {
|
|
200
|
+
this.debug('Emitting "AudioStreamNodeDetachedEvent".');
|
|
201
|
+
this.onEvent(new AudioStreamNodeDetachedEvent(this.id(), audioNodeId));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Emits an `AudioSourceOffEvent`. */
|
|
205
|
+
protected emitOff(): void {
|
|
206
|
+
this.debug('Emitting "AudioSourceOffEvent".');
|
|
207
|
+
this.onEvent(new AudioSourceOffEvent(this.id()));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Speech SDK quirks: Although "close" is marked as abstract, it is never called in our observations.
|
|
211
|
+
// ESLint: Speech SDK requires this function, but we are not implementing it.
|
|
212
|
+
close(): void {
|
|
213
|
+
this.debug('Callback for "close".');
|
|
214
|
+
|
|
215
|
+
throw new Error('Not implemented');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Speech SDK quirks: Although "turnOn" is implemented in Speech SDK Push/PullAudioInputStream, it is never called in our observations.
|
|
219
|
+
turnOn(): void {
|
|
220
|
+
this.debug('Callback for "turnOn".');
|
|
221
|
+
|
|
222
|
+
throw new Error('Not implemented');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Speech SDK quirks: Although "detach" is implemented in Speech SDK Push/PullAudioInputStream, it is never called in our observations.
|
|
226
|
+
detach(): void {
|
|
227
|
+
this.debug('Callback for "detach".');
|
|
228
|
+
|
|
229
|
+
throw new Error('Not implemented');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Log the message to console if `debug` is set to `true`. */
|
|
233
|
+
private debug(message, ...args) {
|
|
234
|
+
// ESLint: For debugging, will only log when "debug" is set to "true".
|
|
235
|
+
// False alarm: indexer is a constant of type Symbol.
|
|
236
|
+
// eslint-disable-next-line no-console, security/detect-object-injection
|
|
237
|
+
this[SYMBOL_OPTIONS].debug && console.info(`CustomAudioInputStream: ${message}`, ...args);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Implements this function. When called, it should start recording and return an `IAudioStreamNode`. */
|
|
241
|
+
protected abstract performAttach(audioNodeId: string): Promise<{
|
|
242
|
+
audioStreamNode: AudioStreamNode;
|
|
243
|
+
deviceInfo: DeviceInfo;
|
|
244
|
+
format: Format;
|
|
245
|
+
}>;
|
|
246
|
+
|
|
247
|
+
/** Attaches the device by returning an audio node. */
|
|
248
|
+
attach(audioNodeId: string): Promise<AudioStreamNode> {
|
|
249
|
+
this.debug(`Callback for "attach" with "${audioNodeId}".`);
|
|
250
|
+
|
|
251
|
+
this.emitNodeAttaching(audioNodeId);
|
|
252
|
+
|
|
253
|
+
return Promise.resolve().then<AudioStreamNode>(async () => {
|
|
254
|
+
this.emitInitializing();
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
const { audioStreamNode, deviceInfo, format } = await this.performAttach(audioNodeId);
|
|
258
|
+
|
|
259
|
+
// Although only getter of "format" is called before "attach" (in Direct Line Speech),
|
|
260
|
+
// we are handling both "deviceInfo" and "format" in similar way for uniformity.
|
|
261
|
+
|
|
262
|
+
// False alarm: indexer is a constant of type Symbol.
|
|
263
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
264
|
+
this[SYMBOL_DEVICE_INFO_DEFERRED].resolve(deviceInfo);
|
|
265
|
+
|
|
266
|
+
// False alarm: indexer is a constant of type Symbol.
|
|
267
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
268
|
+
this[SYMBOL_FORMAT_DEFERRED].resolve(
|
|
269
|
+
new AudioStreamFormatImpl(format.samplesPerSec, format.bitsPerSample, format.channels)
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
this.emitReady();
|
|
273
|
+
this.emitNodeAttached(audioNodeId);
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
detach: async () => {
|
|
277
|
+
this.debug(`Detaching audio node "${audioNodeId}".`);
|
|
278
|
+
|
|
279
|
+
await audioStreamNode.detach();
|
|
280
|
+
|
|
281
|
+
// Speech SDK quirks: Since "turnOff" is not called in Direct Line Speech, we will emit event "source off" here instead.
|
|
282
|
+
this.emitOff();
|
|
283
|
+
this.emitNodeDetached(audioNodeId);
|
|
284
|
+
},
|
|
285
|
+
id: () => audioStreamNode.id(),
|
|
286
|
+
read: () => {
|
|
287
|
+
this.debug('Reading');
|
|
288
|
+
|
|
289
|
+
return audioStreamNode.read();
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
} catch (error) {
|
|
293
|
+
this.emitNodeError(audioNodeId, error);
|
|
294
|
+
|
|
295
|
+
throw error;
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Implements this function. When called, it should stop recording. This is called before the `IAudioStreamNode.detach` function.
|
|
302
|
+
*
|
|
303
|
+
* Note: when using with Direct Line Speech, this function is never called.
|
|
304
|
+
*/
|
|
305
|
+
|
|
306
|
+
// ESLint: We are not implementing this function because it is not called by Direct Line Speech.
|
|
307
|
+
// eslint-disable-next-line class-methods-use-this
|
|
308
|
+
protected performTurnOff(): Promise<void> {
|
|
309
|
+
// ESLint: "return" is required by TypeScript
|
|
310
|
+
// eslint-disable-next-line no-useless-return
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** Turn off the audio device. This is called before detaching from the graph. */
|
|
315
|
+
// Speech SDK quirks: It is confused to have both "turnOff" and "detach". "turnOff" is called before "detach".
|
|
316
|
+
// Why don't we put all logics at "detach"?
|
|
317
|
+
// Speech SDK quirks: Direct Line Speech never call "turnOff". "Source off" event need to be emitted during "detach" instead.
|
|
318
|
+
// Also, custom implementation should be done at "detach" instead, such as ending and closing output streams.
|
|
319
|
+
async turnOff(): Promise<void> {
|
|
320
|
+
this.debug(`Callback for "turnOff".`);
|
|
321
|
+
|
|
322
|
+
await this.performTurnOff();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** Gets the device information. */
|
|
326
|
+
// ESLint: This code will only works in browsers other than IE11. Only works in ES5 is okay.
|
|
327
|
+
// @ts-ignore Accessors are only available when targeting ECMAScript 5 and higher.ts(1056)
|
|
328
|
+
get deviceInfo(): Promise<ISpeechConfigAudioDevice> {
|
|
329
|
+
this.debug(`Getting "deviceInfo".`);
|
|
330
|
+
|
|
331
|
+
// False alarm: indexer is a constant of type Symbol.
|
|
332
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
333
|
+
return Promise.all([this[SYMBOL_DEVICE_INFO_DEFERRED].promise, this[SYMBOL_FORMAT_DEFERRED].promise]).then(
|
|
334
|
+
([{ connectivity, manufacturer, model, type }, { bitsPerSample, channels, samplesPerSec }]) => ({
|
|
335
|
+
bitspersample: bitsPerSample,
|
|
336
|
+
channelcount: channels,
|
|
337
|
+
connectivity:
|
|
338
|
+
typeof connectivity === 'string' && !isForbiddenPropertyName(connectivity)
|
|
339
|
+
? // Mitigated through denylisting.
|
|
340
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
341
|
+
Connectivity[connectivity]
|
|
342
|
+
: connectivity || Connectivity.Unknown,
|
|
343
|
+
manufacturer: manufacturer || '',
|
|
344
|
+
model: model || '',
|
|
345
|
+
samplerate: samplesPerSec,
|
|
346
|
+
// Mitigated through denylisting.
|
|
347
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
348
|
+
type: typeof type === 'string' && !isForbiddenPropertyName(type) ? Type[type] : type || Type.Unknown
|
|
349
|
+
})
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export default CustomAudioInputStream;
|
|
355
|
+
|
|
356
|
+
export type { AudioStreamNode, DeviceInfo, Format, Options };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* eslint no-empty-function: "off" */
|
|
2
|
+
|
|
3
|
+
import createAudioConfig from './createAudioConfig';
|
|
4
|
+
|
|
5
|
+
describe('Verify arguments', () => {
|
|
6
|
+
test('should not throw if only "attach" is supplied', () => {
|
|
7
|
+
expect(() => createAudioConfig({ attach: () => {} })).not.toThrow();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test('should not throw if both "attach" and "turnOff" are supplied', () => {
|
|
11
|
+
expect(() => createAudioConfig({ attach: () => {}, turnOff: () => {} })).not.toThrow();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('should throw if "attach" is not supplied', () => {
|
|
15
|
+
expect(() => createAudioConfig({})).toThrow('"attach" must be a function.');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('should throw if "turnOff" is not a function', () => {
|
|
19
|
+
expect(() => createAudioConfig({ attach: () => {}, turnOff: '123' })).toThrow(
|
|
20
|
+
'"turnOff", if defined, must be a function.'
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// TODO: [P2] #3976 We should export this to allow web developers to bring in their own microphone.
|
|
2
|
+
// For example, it should enable React Native devs to bring in their microphone implementation and use Cognitive Services Speech Services.
|
|
3
|
+
|
|
4
|
+
import { AudioConfig } from 'microsoft-cognitiveservices-speech-sdk';
|
|
5
|
+
|
|
6
|
+
import CustomAudioInputStream, { AudioStreamNode, DeviceInfo, Format } from './CustomAudioInputStream';
|
|
7
|
+
|
|
8
|
+
type AttachFunction = (audioNodeId: string) => Promise<{
|
|
9
|
+
audioStreamNode: AudioStreamNode;
|
|
10
|
+
deviceInfo: DeviceInfo;
|
|
11
|
+
format: Format;
|
|
12
|
+
}>;
|
|
13
|
+
|
|
14
|
+
type TurnOffFunction = () => Promise<void>;
|
|
15
|
+
|
|
16
|
+
const SYMBOL_ATTACH = Symbol('attach');
|
|
17
|
+
const SYMBOL_TURN_OFF = Symbol('turnOff');
|
|
18
|
+
|
|
19
|
+
type CreateAudioConfigOptions = {
|
|
20
|
+
/** Callback function for attaching the device by returning an audio node. */
|
|
21
|
+
attach: AttachFunction;
|
|
22
|
+
|
|
23
|
+
/** `true` to enable diagnostic information, otherwise, `false`. */
|
|
24
|
+
debug?: true;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Callback function for turning off the device before detaching its node from an audio graph.
|
|
28
|
+
*
|
|
29
|
+
* Note: this is not called for Direct Line Speech.
|
|
30
|
+
*/
|
|
31
|
+
turnOff?: TurnOffFunction;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
class CreateAudioConfigAudioInputStream extends CustomAudioInputStream {
|
|
35
|
+
constructor({ attach, debug, turnOff }: CreateAudioConfigOptions) {
|
|
36
|
+
if (!attach || typeof attach !== 'function') {
|
|
37
|
+
throw new Error('"attach" must be a function.');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (turnOff && typeof turnOff !== 'function') {
|
|
41
|
+
throw new Error('"turnOff", if defined, must be a function.');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
super({ debug });
|
|
45
|
+
|
|
46
|
+
// False alarm: indexer is a constant of type Symbol.
|
|
47
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
48
|
+
this[SYMBOL_ATTACH] = attach;
|
|
49
|
+
|
|
50
|
+
// False alarm: indexer is a constant of type Symbol.
|
|
51
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
52
|
+
this[SYMBOL_TURN_OFF] = turnOff;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
[SYMBOL_ATTACH]: AttachFunction;
|
|
56
|
+
[SYMBOL_TURN_OFF]: TurnOffFunction;
|
|
57
|
+
|
|
58
|
+
protected performAttach(audioNodeId: string): Promise<{
|
|
59
|
+
audioStreamNode: AudioStreamNode;
|
|
60
|
+
deviceInfo: DeviceInfo;
|
|
61
|
+
format: Format;
|
|
62
|
+
}> {
|
|
63
|
+
// False alarm: indexer is a constant of type Symbol.
|
|
64
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
65
|
+
return this[SYMBOL_ATTACH](audioNodeId);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
protected performTurnOff(): Promise<void> {
|
|
69
|
+
const { [SYMBOL_TURN_OFF]: turnOff } = this;
|
|
70
|
+
|
|
71
|
+
return turnOff && turnOff();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export default function createAudioConfig(options: CreateAudioConfigOptions) {
|
|
76
|
+
return AudioConfig.fromStreamInput(new CreateAudioConfigAudioInputStream(options));
|
|
77
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Creates an AudioContext object. */
|
|
2
|
+
export default function createAudioContext(): AudioContext {
|
|
3
|
+
if (typeof window.AudioContext !== 'undefined') {
|
|
4
|
+
return new window.AudioContext();
|
|
5
|
+
|
|
6
|
+
// Required by TypeScript.
|
|
7
|
+
// eslint-disable-next-line dot-notation
|
|
8
|
+
} else if (typeof window['webkitAudioContext'] !== 'undefined') {
|
|
9
|
+
// This is for Safari as Web Audio API is still under vendor-prefixed.
|
|
10
|
+
// eslint-disable-next-line dot-notation
|
|
11
|
+
return new window['webkitAudioContext']();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// TODO: Fix this.
|
|
15
|
+
throw new Error('This browser does not support Web Audio API.');
|
|
16
|
+
}
|