-
Notifications
You must be signed in to change notification settings - Fork 77
feat: handle command injection optionally #1548
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
971ed00
318e03c
04988f0
780c52c
84d861a
c095a38
0b3eccd
9c9644e
274793c
b72490c
ad400a8
febad52
d2d54ac
3e7229e
5983996
19355ce
ef19c9d
136eb09
0a53752
cb2ada6
114e367
fdb352b
aacb8a9
cf88672
e14c318
f17349e
d525ae3
19edce6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| import type { MessageComposer } from '../../messageComposer'; | ||
| import type { | ||
| MessageComposerMiddlewareState, | ||
| MessageCompositionMiddleware, | ||
| MessageDraftComposerMiddlewareValueState, | ||
| MessageDraftCompositionMiddleware, | ||
| } from '../messageComposer/types'; | ||
| import type { MiddlewareHandlerParams } from '../../../middleware'; | ||
|
|
||
| export const createCommandInjectionMiddleware = ( | ||
| composer: MessageComposer, | ||
| ): MessageCompositionMiddleware => ({ | ||
| handlers: { | ||
| compose: ({ | ||
| complete, | ||
| forward, | ||
| state, | ||
| }: MiddlewareHandlerParams<MessageComposerMiddlewareState>) => { | ||
| const command = composer.textComposer.command; | ||
| if (!command) { | ||
| return forward(); | ||
| } | ||
| const { text } = state.localMessage; | ||
|
|
||
| const injection = `/${command?.name}`; | ||
| const enrichedText = `${injection} ${text}`; | ||
|
|
||
| return complete({ | ||
| ...state, | ||
| localMessage: { | ||
| ...state.localMessage, | ||
| text: enrichedText, | ||
| }, | ||
| message: { | ||
| ...state.message, | ||
| text: enrichedText, | ||
| }, | ||
| }); | ||
| }, | ||
| }, | ||
| id: 'stream-io/message-composer-middleware/command-injection', | ||
khushal87 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }); | ||
|
|
||
| export const createDraftCommandInjectionMiddleware = ( | ||
| composer: MessageComposer, | ||
| ): MessageDraftCompositionMiddleware => ({ | ||
| handlers: { | ||
| compose: ({ | ||
| forward, | ||
| state, | ||
| complete, | ||
| }: MiddlewareHandlerParams<MessageDraftComposerMiddlewareValueState>) => { | ||
| const command = composer.textComposer.command; | ||
| if (!command) { | ||
| return forward(); | ||
| } | ||
| const { text } = state.draft; | ||
|
|
||
| const injection = `/${command?.name}`; | ||
| const enrichedText = `${injection} ${text}`; | ||
|
|
||
| return complete({ | ||
| ...state, | ||
| draft: { | ||
| ...state.draft, | ||
| text: enrichedText, | ||
| }, | ||
| }); | ||
| }, | ||
| }, | ||
| id: 'stream-io/message-composer-middleware/draft-command-injection', | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| import type { Middleware } from '../../../middleware'; | ||
| import type { TextComposerMiddlewareExecutorState } from './TextComposerMiddlewareExecutor'; | ||
| import type { CommandSuggestion } from './types'; | ||
|
|
||
| export type ApplyCommandSettingsMiddleware = Middleware< | ||
| TextComposerMiddlewareExecutorState<CommandSuggestion>, | ||
| 'onChange' | 'onSuggestionItemSelect' | ||
| >; | ||
|
|
||
| export const createApplyCommandSettingsMiddleware = | ||
khushal87 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| (): ApplyCommandSettingsMiddleware => ({ | ||
| handlers: { | ||
| onChange: ({ complete, forward, state }) => { | ||
| const { command } = state; | ||
|
|
||
| if (!command) { | ||
| return forward(); | ||
| } | ||
|
|
||
| const trigger = `/${command.name}`; | ||
khushal87 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| const newText = state.text.replace(new RegExp(`^${trigger}(\\s|$)`), ''); | ||
khushal87 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| return complete({ | ||
| ...state, | ||
| selection: { | ||
| end: state.selection.end - trigger.length, | ||
| start: state.selection.start - trigger.length, | ||
| }, | ||
| suggestions: undefined, | ||
| text: newText, | ||
| }); | ||
| }, | ||
| onSuggestionItemSelect: ({ complete, forward, state }) => { | ||
| const { command } = state; | ||
|
|
||
| if (!command) { | ||
| return forward(); | ||
| } | ||
|
|
||
| const trigger = `/${command?.name} `; | ||
|
|
||
| const newText = state.text.slice(trigger.length); | ||
| return complete({ | ||
khushal87 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ...state, | ||
| selection: { | ||
| end: state.selection.end - trigger.length, | ||
| start: state.selection.start - trigger.length, | ||
| }, | ||
| suggestions: undefined, | ||
khushal87 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| text: newText, | ||
| }); | ||
| }, | ||
| }, | ||
| id: 'stream-io/text-composer/apply-command-settings', | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -134,3 +134,13 @@ export const getTokenizedSuggestionDisplayName = ({ | |
| : [displayName], | ||
| }, | ||
| }); | ||
|
|
||
| export const isTextMatched = (input: string, command: string): boolean => { | ||
|
||
| try { | ||
| const regex = new RegExp(`^${escapeRegExp(command)}`, 'i'); | ||
| return regex.test(input); | ||
| } catch (error) { | ||
| console.error('Error in validating with the regex:', error); | ||
| return false; | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,7 @@ import type { TextSelection } from './middleware/textComposer/types'; | |
| import type { TextComposerState } from './middleware/textComposer/types'; | ||
| import type { Suggestions } from './middleware/textComposer/types'; | ||
| import type { MessageComposer } from './messageComposer'; | ||
| import type { DraftMessage, LocalMessage, UserResponse } from '../types'; | ||
| import type { CommandResponse, DraftMessage, LocalMessage, UserResponse } from '../types'; | ||
|
|
||
| export type TextComposerOptions = { | ||
| composer: MessageComposer; | ||
|
|
@@ -37,6 +37,7 @@ const initState = ({ | |
| if (!message) { | ||
| const text = composer.config.text.defaultValue ?? ''; | ||
| return { | ||
| command: null, | ||
| mentionedUsers: [], | ||
| text, | ||
| selection: { start: text.length, end: text.length }, | ||
|
|
@@ -118,6 +119,10 @@ export class TextComposer { | |
|
|
||
| // --- START STATE API --- | ||
|
|
||
| get command() { | ||
| return this.state.getLatestValue().command; | ||
| } | ||
|
|
||
| get mentionedUsers() { | ||
| return this.state.getLatestValue().mentionedUsers; | ||
| } | ||
|
|
@@ -146,6 +151,10 @@ export class TextComposer { | |
| this.state.partialNext({ mentionedUsers: users }); | ||
| } | ||
|
|
||
| clearCommand() { | ||
| this.state.partialNext({ command: null }); | ||
| } | ||
|
|
||
| upsertMentionedUser = (user: UserResponse) => { | ||
| const mentionedUsers = [...this.mentionedUsers]; | ||
| const existingUserIndex = mentionedUsers.findIndex((u) => u.id === user.id); | ||
|
|
@@ -169,6 +178,11 @@ export class TextComposer { | |
| this.state.partialNext({ mentionedUsers }); | ||
| }; | ||
|
|
||
| setCommand = (command: CommandResponse | null) => { | ||
| if (command === this.command) return; | ||
|
||
| this.state.partialNext({ command }); | ||
| }; | ||
|
|
||
| setText = (text: string) => { | ||
| if (!this.enabled || text === this.text) return; | ||
| this.state.partialNext({ text }); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.