MM-53340 - Create registerDesktopNotificationHook for plugins, e.g. Calls (#23884)
* calls will notify in GM/DM channels for call started posts * fix types * add registerDesktopNotificationHook * remove callsWillNotify * cleanup unused types * use an args object to shorten params * add CUSTOM_CALLS_RECORDING to constants
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
f45f774ece
Коммит
46a659e06d
@@ -89,3 +89,31 @@ export function runMessageWillBeUpdatedHooks(newPost, oldPost) {
|
|||||||
return {data: post};
|
return {data: post};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function runDesktopNotificationHooks(post, msgProps, channel, teamId, args) {
|
||||||
|
return async (dispatch, getState) => {
|
||||||
|
const hooks = getState().plugins.components.DesktopNotificationHooks;
|
||||||
|
if (!hooks || hooks.length === 0) {
|
||||||
|
return {args};
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextArgs = args;
|
||||||
|
for (const hook of hooks) {
|
||||||
|
const result = await hook.hook(post, msgProps, channel, teamId, nextArgs); // eslint-disable-line no-await-in-loop
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
if (result.error) {
|
||||||
|
return {error: result.error};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.args) {
|
||||||
|
return {error: 'returned empty args'};
|
||||||
|
}
|
||||||
|
|
||||||
|
nextArgs = result.args;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {args: nextArgs};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {General} from 'mattermost-redux/constants';
|
||||||
import mockStore from 'tests/test_store';
|
import mockStore from 'tests/test_store';
|
||||||
|
|
||||||
import {runMessageWillBePostedHooks, runMessageWillBeUpdatedHooks, runSlashCommandWillBePostedHooks} from './hooks';
|
import {
|
||||||
|
runDesktopNotificationHooks,
|
||||||
|
runMessageWillBePostedHooks,
|
||||||
|
runMessageWillBeUpdatedHooks,
|
||||||
|
runSlashCommandWillBePostedHooks,
|
||||||
|
} from './hooks';
|
||||||
|
|
||||||
describe('runMessageWillBePostedHooks', () => {
|
describe('runMessageWillBePostedHooks', () => {
|
||||||
test('should do nothing when no hooks are registered', async () => {
|
test('should do nothing when no hooks are registered', async () => {
|
||||||
@@ -493,3 +499,267 @@ describe('runMessageWillBeUpdatedHooks', () => {
|
|||||||
expect(hook2).toHaveBeenCalledWith(newPost, oldPost);
|
expect(hook2).toHaveBeenCalledWith(newPost, oldPost);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('runDesktopNotificationHooks', () => {
|
||||||
|
test('should do nothing when no hooks are registered', async () => {
|
||||||
|
const store = mockStore({
|
||||||
|
plugins: {
|
||||||
|
components: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const post = {id: 'postid01234567890123456789'};
|
||||||
|
const teamId = 'teamid01234567890123456789';
|
||||||
|
const msgProps = {mentions: ['userid1'], team_id: teamId};
|
||||||
|
const channel = {type: General.DM_CHANNEL};
|
||||||
|
const args = {
|
||||||
|
title: 'Notification title',
|
||||||
|
body: 'Notification body',
|
||||||
|
silent: false,
|
||||||
|
soundName: 'Bing',
|
||||||
|
url: 'http://localhost:8065/ad-1/channels/test',
|
||||||
|
notify: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await store.dispatch(runDesktopNotificationHooks(post, msgProps, channel, teamId, args));
|
||||||
|
|
||||||
|
expect(result.args).toEqual(args);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should pass the args through every hook', async () => {
|
||||||
|
const hook1 = jest.fn((post, msgProps, channel, teamId, args) => ({args}));
|
||||||
|
const hook2 = jest.fn((post, msgProps, channel, teamId, args) => ({args}));
|
||||||
|
const hook3 = jest.fn((post, msgProps, channel, teamId, args) => ({args}));
|
||||||
|
|
||||||
|
const store = mockStore({
|
||||||
|
plugins: {
|
||||||
|
components: {
|
||||||
|
DesktopNotificationHooks: [
|
||||||
|
{hook: hook1},
|
||||||
|
{hook: hook2},
|
||||||
|
{hook: hook3},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const post = {id: 'postid01234567890123456789'};
|
||||||
|
const teamId = 'teamid01234567890123456789';
|
||||||
|
const msgProps = {mentions: ['userid1'], team_id: teamId};
|
||||||
|
const channel = {type: General.DM_CHANNEL};
|
||||||
|
const args = {
|
||||||
|
title: 'Notification title',
|
||||||
|
body: 'Notification body',
|
||||||
|
silent: false,
|
||||||
|
soundName: 'Bing',
|
||||||
|
url: 'http://localhost:8065/ad-1/channels/test',
|
||||||
|
notify: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await store.dispatch(runDesktopNotificationHooks(post, msgProps, channel, teamId, args));
|
||||||
|
|
||||||
|
expect(result.args).toEqual(args);
|
||||||
|
expect(hook1).toHaveBeenCalledWith(post, msgProps, channel, teamId, args);
|
||||||
|
expect(hook2).toHaveBeenCalledWith(post, msgProps, channel, teamId, args);
|
||||||
|
expect(hook3).toHaveBeenCalledWith(post, msgProps, channel, teamId, args);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return an error when a hook returns an error', async () => {
|
||||||
|
const hook1 = jest.fn((post, msgProps, channel, teamId, args) => ({args}));
|
||||||
|
const hook2 = jest.fn(() => ({error: 'an error occurred'}));
|
||||||
|
const hook3 = jest.fn((post, msgProps, channel, teamId, args) => ({args}));
|
||||||
|
|
||||||
|
const store = mockStore({
|
||||||
|
plugins: {
|
||||||
|
components: {
|
||||||
|
DesktopNotificationHooks: [
|
||||||
|
{hook: hook1},
|
||||||
|
{hook: hook2},
|
||||||
|
{hook: hook3},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const post = {id: 'postid01234567890123456789'};
|
||||||
|
const teamId = 'teamid01234567890123456789';
|
||||||
|
const msgProps = {mentions: ['userid1'], team_id: teamId};
|
||||||
|
const channel = {type: General.DM_CHANNEL};
|
||||||
|
const args = {
|
||||||
|
title: 'Notification title',
|
||||||
|
body: 'Notification body',
|
||||||
|
silent: false,
|
||||||
|
soundName: 'Bing',
|
||||||
|
url: 'http://localhost:8065/ad-1/channels/test',
|
||||||
|
notify: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await store.dispatch(runDesktopNotificationHooks(post, msgProps, channel, teamId, args));
|
||||||
|
|
||||||
|
expect(result).toEqual({error: 'an error occurred'});
|
||||||
|
expect(hook1).toHaveBeenCalledWith(post, msgProps, channel, teamId, args);
|
||||||
|
expect(hook2).toHaveBeenCalledWith(post, msgProps, channel, teamId, args);
|
||||||
|
expect(hook3).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return an error when a hook returns an empty result', async () => {
|
||||||
|
const hook1 = jest.fn((post, msgProps, channel, teamId, args) => ({args}));
|
||||||
|
const hook2 = jest.fn(() => ({}));
|
||||||
|
const hook3 = jest.fn((post, msgProps, channel, teamId, args) => ({args}));
|
||||||
|
|
||||||
|
const store = mockStore({
|
||||||
|
plugins: {
|
||||||
|
components: {
|
||||||
|
DesktopNotificationHooks: [
|
||||||
|
{hook: hook1},
|
||||||
|
{hook: hook2},
|
||||||
|
{hook: hook3},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const post = {id: 'postid01234567890123456789'};
|
||||||
|
const teamId = 'teamid01234567890123456789';
|
||||||
|
const msgProps = {mentions: ['userid1'], team_id: teamId};
|
||||||
|
const channel = {type: General.DM_CHANNEL};
|
||||||
|
const args = {
|
||||||
|
title: 'Notification title',
|
||||||
|
body: 'Notification body',
|
||||||
|
silent: false,
|
||||||
|
soundName: 'Bing',
|
||||||
|
url: 'http://localhost:8065/ad-1/channels/test',
|
||||||
|
notify: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await store.dispatch(runDesktopNotificationHooks(post, msgProps, channel, teamId, args));
|
||||||
|
|
||||||
|
expect(result).toEqual({error: 'returned empty args'});
|
||||||
|
expect(hook1).toHaveBeenCalledWith(post, msgProps, channel, teamId, args);
|
||||||
|
expect(hook2).toHaveBeenCalledWith(post, msgProps, channel, teamId, args);
|
||||||
|
expect(hook3).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should continue to call next hooks when a hook returns null or undefined', async () => {
|
||||||
|
const hook1 = jest.fn(() => (null));
|
||||||
|
const hook2 = jest.fn(() => (undefined));
|
||||||
|
const hook3 = jest.fn((post, msgProps, channel, teamId, args) => ({args}));
|
||||||
|
|
||||||
|
const store = mockStore({
|
||||||
|
plugins: {
|
||||||
|
components: {
|
||||||
|
DesktopNotificationHooks: [
|
||||||
|
{hook: hook1},
|
||||||
|
{hook: hook2},
|
||||||
|
{hook: hook3},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const post = {id: 'postid01234567890123456789'};
|
||||||
|
const teamId = 'teamid01234567890123456789';
|
||||||
|
const msgProps = {mentions: ['userid1'], team_id: teamId};
|
||||||
|
const channel = {type: General.DM_CHANNEL};
|
||||||
|
const args = {
|
||||||
|
title: 'Notification title',
|
||||||
|
body: 'Notification body',
|
||||||
|
silent: false,
|
||||||
|
soundName: 'Bing',
|
||||||
|
url: 'http://localhost:8065/ad-1/channels/test',
|
||||||
|
notify: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await store.dispatch(runDesktopNotificationHooks(post, msgProps, channel, teamId, args));
|
||||||
|
|
||||||
|
expect(result.args).toEqual(args);
|
||||||
|
expect(hook1).toHaveBeenCalledWith(post, msgProps, channel, teamId, args);
|
||||||
|
expect(hook2).toHaveBeenCalledWith(post, msgProps, channel, teamId, args);
|
||||||
|
expect(hook3).toHaveBeenCalledWith(post, msgProps, channel, teamId, args);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should pass the result of each hook to the next', async () => {
|
||||||
|
const hook1 = jest.fn((post, msgProps, channel, teamId, args) => ({args: {...args, title: args.title + 'a'}}));
|
||||||
|
const hook2 = jest.fn((post, msgProps, channel, teamId, args) => ({
|
||||||
|
args: {
|
||||||
|
...args,
|
||||||
|
title: args.title + 'b',
|
||||||
|
notify: false,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
const hook3 = jest.fn((post, msgProps, channel, teamId, args) => ({
|
||||||
|
args: {
|
||||||
|
...args,
|
||||||
|
title: args.title + 'c',
|
||||||
|
notify: true,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const store = mockStore({
|
||||||
|
plugins: {
|
||||||
|
components: {
|
||||||
|
DesktopNotificationHooks: [
|
||||||
|
{hook: hook1},
|
||||||
|
{hook: hook2},
|
||||||
|
{hook: hook3},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const post = {id: 'postid01234567890123456789'};
|
||||||
|
const teamId = 'teamid01234567890123456789';
|
||||||
|
const msgProps = {mentions: ['userid1'], team_id: teamId};
|
||||||
|
const channel = {type: General.DM_CHANNEL};
|
||||||
|
const args = {
|
||||||
|
title: 'Notification title',
|
||||||
|
body: 'Notification body',
|
||||||
|
silent: false,
|
||||||
|
soundName: 'Bing',
|
||||||
|
url: 'http://localhost:8065/ad-1/channels/test',
|
||||||
|
notify: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await store.dispatch(runDesktopNotificationHooks(post, msgProps, channel, teamId, args));
|
||||||
|
|
||||||
|
expect(result.args).toEqual({...args, title: 'Notification titleabc', notify: true});
|
||||||
|
expect(hook1).toHaveBeenCalledWith(post, msgProps, channel, teamId, args);
|
||||||
|
expect(hook2).toHaveBeenCalledWith(post, msgProps, channel, teamId, {...args, title: 'Notification titlea'});
|
||||||
|
expect(hook3).toHaveBeenCalledWith(post, msgProps, channel, teamId, {...args, title: 'Notification titleab', notify: false});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should wait for async hooks', async () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
|
const hook = jest.fn((post, msgProps, channel, teamId, args) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve({args: {...args, title: args.title + ' async'}});
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
jest.runOnlyPendingTimers();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = mockStore({
|
||||||
|
plugins: {
|
||||||
|
components: {
|
||||||
|
DesktopNotificationHooks: [
|
||||||
|
{hook},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const post = {id: 'postid01234567890123456789'};
|
||||||
|
const teamId = 'teamid01234567890123456789';
|
||||||
|
const msgProps = {mentions: ['userid1'], team_id: teamId};
|
||||||
|
const channel = {type: General.DM_CHANNEL};
|
||||||
|
const args = {
|
||||||
|
title: 'Notification title',
|
||||||
|
body: 'Notification body',
|
||||||
|
silent: false,
|
||||||
|
soundName: 'Bing',
|
||||||
|
url: 'http://localhost:8065/ad-1/channels/test',
|
||||||
|
notify: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await store.dispatch(runDesktopNotificationHooks(post, msgProps, channel, teamId, args));
|
||||||
|
|
||||||
|
expect(result.args).toEqual({...args, title: 'Notification title async'});
|
||||||
|
expect(hook).toHaveBeenCalledWith(post, msgProps, channel, teamId, args);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import {logError} from 'mattermost-redux/actions/errors';
|
|||||||
import {getProfilesByIds} from 'mattermost-redux/actions/users';
|
import {getProfilesByIds} from 'mattermost-redux/actions/users';
|
||||||
import {getCurrentChannel, getMyChannelMember, makeGetChannel} from 'mattermost-redux/selectors/entities/channels';
|
import {getCurrentChannel, getMyChannelMember, makeGetChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||||
import {getTeammateNameDisplaySetting, isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences';
|
import {
|
||||||
|
getTeammateNameDisplaySetting,
|
||||||
|
isCollapsedThreadsEnabled,
|
||||||
|
} from 'mattermost-redux/selectors/entities/preferences';
|
||||||
import {getCurrentUserId, getCurrentUser, getStatusForUserId, getUser} from 'mattermost-redux/selectors/entities/users';
|
import {getCurrentUserId, getCurrentUser, getStatusForUserId, getUser} from 'mattermost-redux/selectors/entities/users';
|
||||||
import {isChannelMuted} from 'mattermost-redux/utils/channel_utils';
|
import {isChannelMuted} from 'mattermost-redux/utils/channel_utils';
|
||||||
import {isSystemMessage, isUserAddedInChannel} from 'mattermost-redux/utils/post_utils';
|
import {isSystemMessage, isUserAddedInChannel} from 'mattermost-redux/utils/post_utils';
|
||||||
@@ -22,6 +25,7 @@ import {isDesktopApp, isMobileApp, isWindowsApp} from 'utils/user_agent';
|
|||||||
import * as Utils from 'utils/utils';
|
import * as Utils from 'utils/utils';
|
||||||
import {t} from 'utils/i18n';
|
import {t} from 'utils/i18n';
|
||||||
import {stripMarkdown} from 'utils/markdown';
|
import {stripMarkdown} from 'utils/markdown';
|
||||||
|
import {runDesktopNotificationHooks} from './hooks';
|
||||||
|
|
||||||
const NOTIFY_TEXT_MAX_LENGTH = 50;
|
const NOTIFY_TEXT_MAX_LENGTH = 50;
|
||||||
|
|
||||||
@@ -190,17 +194,28 @@ export function sendDesktopNotification(post, msgProps) {
|
|||||||
}
|
}
|
||||||
notify = notify || !state.views.browser.focused;
|
notify = notify || !state.views.browser.focused;
|
||||||
|
|
||||||
const soundName = getNotificationSoundFromChannelMemberAndUser(member, user);
|
let soundName = getNotificationSoundFromChannelMemberAndUser(member, user);
|
||||||
|
|
||||||
|
const updatedState = getState();
|
||||||
|
let url = getChannelURL(updatedState, channel, teamId);
|
||||||
|
|
||||||
|
if (isCrtReply) {
|
||||||
|
url = getPermalinkURL(updatedState, teamId, post.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow plugins to change the notification, or re-enable a notification
|
||||||
|
const args = {title, body, silent: !sound, soundName, url, notify};
|
||||||
|
const hookResult = await dispatch(runDesktopNotificationHooks(post, msgProps, channel, teamId, args));
|
||||||
|
if (hookResult.error) {
|
||||||
|
dispatch(logError(hookResult.error));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let silent = false;
|
||||||
|
({title, body, silent, soundName, url, notify} = hookResult.args);
|
||||||
|
|
||||||
if (notify) {
|
if (notify) {
|
||||||
const updatedState = getState();
|
dispatch(notifyMe(title, body, channel, teamId, silent, soundName, url));
|
||||||
let url = getChannelURL(updatedState, channel, teamId);
|
|
||||||
|
|
||||||
if (isCrtReply) {
|
|
||||||
url = getPermalinkURL(updatedState, teamId, post.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(notifyMe(title, body, channel, teamId, !sound, soundName, url));
|
|
||||||
|
|
||||||
//Don't add extra sounds on native desktop clients
|
//Don't add extra sounds on native desktop clients
|
||||||
if (sound && !isDesktopApp() && !isMobileApp()) {
|
if (sound && !isDesktopApp() && !isMobileApp()) {
|
||||||
|
|||||||
@@ -153,6 +153,11 @@ describe('notification_actions', () => {
|
|||||||
isSidebarOpen: true,
|
isSidebarOpen: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
plugins: {
|
||||||
|
components: {
|
||||||
|
DesktopNotificationHooks: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {ActionFunc} from 'mattermost-redux/types/actions';
|
|||||||
import {GlobalState} from 'types/store';
|
import {GlobalState} from 'types/store';
|
||||||
|
|
||||||
import UserSettingsNotifications, {Props} from './user_settings_notifications';
|
import UserSettingsNotifications, {Props} from './user_settings_notifications';
|
||||||
import {isCallsEnabled, isCallsRingingEnabled} from 'selectors/calls';
|
import {isCallsEnabled, isCallsRingingEnabledOnServer} from 'selectors/calls';
|
||||||
|
|
||||||
function mapStateToProps(state: GlobalState) {
|
function mapStateToProps(state: GlobalState) {
|
||||||
const config = getConfig(state);
|
const config = getConfig(state);
|
||||||
@@ -24,7 +24,7 @@ function mapStateToProps(state: GlobalState) {
|
|||||||
sendPushNotifications,
|
sendPushNotifications,
|
||||||
enableAutoResponder,
|
enableAutoResponder,
|
||||||
isCollapsedThreadsEnabled: isCollapsedThreadsEnabled(state),
|
isCollapsedThreadsEnabled: isCollapsedThreadsEnabled(state),
|
||||||
isCallsRingingEnabled: isCallsEnabled(state, '0.17.0') && isCallsRingingEnabled(state),
|
isCallsRingingEnabled: isCallsEnabled(state, '0.17.0') && isCallsRingingEnabledOnServer(state),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1164,4 +1164,52 @@ export default class PluginRegistry {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Register a hook to intercept desktop notifications before they occur.
|
||||||
|
// Accepts a function to run before the desktop notification is triggered.
|
||||||
|
// The function has the following signature:
|
||||||
|
// (post: Post, msgProps: NewPostMessageProps, channel: Channel,
|
||||||
|
// teamId: string, args: DesktopNotificationArgs) => Promise<{
|
||||||
|
// error?: string;
|
||||||
|
// args?: DesktopNotificationArgs;
|
||||||
|
// }>)
|
||||||
|
//
|
||||||
|
// DesktopNotificationArgs is the following type:
|
||||||
|
// export type DesktopNotificationArgs = {
|
||||||
|
// title: string;
|
||||||
|
// body: string;
|
||||||
|
// silent: boolean;
|
||||||
|
// soundName: string;
|
||||||
|
// url: string;
|
||||||
|
// notify: boolean;
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// To stop a desktop notification and allow subsequent hooks to process the notification, return:
|
||||||
|
// {args: {...args, notify: false}}
|
||||||
|
// To enable a desktop notification and allow subsequent hooks to process the notification, return:
|
||||||
|
// {args: {...args, notify: true}}
|
||||||
|
// To stop a desktop notification and prevent subsequent hooks from processing the notification, return either:
|
||||||
|
// {error: 'log this error'}, or {}
|
||||||
|
// To allow subsequent hooks to process the notification, return:
|
||||||
|
// {args}, or null or undefined (thanks js)
|
||||||
|
//
|
||||||
|
// The args returned by the hook will be used as the args for the next hook, until all hooks are
|
||||||
|
// completed. The resulting args will be used as the arguments for the `notifyMe` function.
|
||||||
|
//
|
||||||
|
// Returns a unique identifier.
|
||||||
|
registerDesktopNotificationHook = reArg(['hook'], ({hook}) => {
|
||||||
|
const id = generateId();
|
||||||
|
|
||||||
|
store.dispatch({
|
||||||
|
type: ActionTypes.RECEIVED_PLUGIN_COMPONENT,
|
||||||
|
name: 'DesktopNotificationHooks',
|
||||||
|
data: {
|
||||||
|
id,
|
||||||
|
pluginId: this.id,
|
||||||
|
hook,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return id;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ const initialComponents: PluginsState['components'] = {
|
|||||||
FilesWillUploadHook: [],
|
FilesWillUploadHook: [],
|
||||||
NeedsTeamComponent: [],
|
NeedsTeamComponent: [],
|
||||||
CreateBoardFromTemplate: [],
|
CreateBoardFromTemplate: [],
|
||||||
|
DesktopNotificationHooks: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
function components(state: PluginsState['components'] = initialComponents, action: GenericAction) {
|
function components(state: PluginsState['components'] = initialComponents, action: GenericAction) {
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ export function isCallsEnabled(state: GlobalState, minVersion = '0.4.2') {
|
|||||||
semver.gte(state.plugins.plugins[suitePluginIds.calls].version || '0.0.0', minVersion));
|
semver.gte(state.plugins.plugins[suitePluginIds.calls].version || '0.0.0', minVersion));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isCallsRingingEnabled(state: GlobalState) {
|
// isCallsRingingEnabledOnServer is the flag for the ringing/notification feature in calls
|
||||||
|
export function isCallsRingingEnabledOnServer(state: GlobalState) {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return Boolean(state[`plugins-${suitePluginIds.calls}`]?.callsConfig?.EnableRinging);
|
return Boolean(state[`plugins-${suitePluginIds.calls}`]?.callsConfig?.EnableRinging);
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import {IconGlyphTypes} from '@mattermost/compass-icons/IconGlyphs';
|
|||||||
import {WebSocketClient} from '@mattermost/client';
|
import {WebSocketClient} from '@mattermost/client';
|
||||||
|
|
||||||
import {GlobalState} from 'types/store';
|
import {GlobalState} from 'types/store';
|
||||||
|
import {Channel} from '@mattermost/types/channels';
|
||||||
|
import {NewPostMessageProps} from 'actions/new_post';
|
||||||
|
|
||||||
export type PluginSiteStatsHandler = () => Promise<Record<string, PluginAnalyticsRow>>;
|
export type PluginSiteStatsHandler = () => Promise<Record<string, PluginAnalyticsRow>>;
|
||||||
|
|
||||||
@@ -37,6 +39,7 @@ export type PluginsState = {
|
|||||||
FilesWillUploadHook: PluginComponent[];
|
FilesWillUploadHook: PluginComponent[];
|
||||||
NeedsTeamComponent: NeedsTeamComponent[];
|
NeedsTeamComponent: NeedsTeamComponent[];
|
||||||
CreateBoardFromTemplate: PluginComponent[];
|
CreateBoardFromTemplate: PluginComponent[];
|
||||||
|
DesktopNotificationHooks: DesktopNotificationHook[];
|
||||||
};
|
};
|
||||||
|
|
||||||
postTypes: {
|
postTypes: {
|
||||||
@@ -65,7 +68,7 @@ export type PluginsState = {
|
|||||||
export type Menu = {
|
export type Menu = {
|
||||||
id: string;
|
id: string;
|
||||||
parentMenuId?: string;
|
parentMenuId?: string;
|
||||||
text?: React.ReactElement|string;
|
text?: React.ReactElement | string;
|
||||||
selectedValueText?: string;
|
selectedValueText?: string;
|
||||||
subMenu?: Menu[];
|
subMenu?: Menu[];
|
||||||
filter?: (id?: string) => boolean;
|
filter?: (id?: string) => boolean;
|
||||||
@@ -113,7 +116,7 @@ export type FilePreviewComponent = {
|
|||||||
id: string;
|
id: string;
|
||||||
pluginId: string;
|
pluginId: string;
|
||||||
override: (fileInfo: FileInfo, post?: Post) => boolean;
|
override: (fileInfo: FileInfo, post?: Post) => boolean;
|
||||||
component: React.ComponentType<{fileInfo: FileInfo; post?: Post; onModalDismissed: () => void}>;
|
component: React.ComponentType<{ fileInfo: FileInfo; post?: Post; onModalDismissed: () => void }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FileDropdownPluginComponent = {
|
export type FileDropdownPluginComponent = {
|
||||||
@@ -220,3 +223,19 @@ export type ProductComponent = {
|
|||||||
*/
|
*/
|
||||||
wrapped: boolean;
|
wrapped: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DesktopNotificationArgs = {
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
silent: boolean;
|
||||||
|
soundName: string;
|
||||||
|
url: string;
|
||||||
|
notify: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DesktopNotificationHook = PluginComponent & {
|
||||||
|
hook: (post: Post, msgProps: NewPostMessageProps, channel: Channel, teamId: string, args: DesktopNotificationArgs) => Promise<{
|
||||||
|
error?: string;
|
||||||
|
args?: DesktopNotificationArgs;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|||||||
@@ -803,6 +803,8 @@ export const PostTypes = {
|
|||||||
REMOVE_LINK_PREVIEW: 'remove_link_preview',
|
REMOVE_LINK_PREVIEW: 'remove_link_preview',
|
||||||
ME: 'me',
|
ME: 'me',
|
||||||
REMINDER: 'reminder',
|
REMINDER: 'reminder',
|
||||||
|
CUSTOM_CALLS: 'custom_calls',
|
||||||
|
CUSTOM_CALLS_RECORDING: 'custom_calls_recording',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StatTypes = keyMirror({
|
export const StatTypes = keyMirror({
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user