Реальные ID двух вручную подготовленных пользователей своей новой базы, собственное обсуждение и настоящая отправка только между ними. Подключение/отключение выполняется двумя API-стилями; callback-копии, серверное сохранение и факт интерактивного действия проверяются отдельно.
Где и как запускать
Главный код — полный модуль своей управляемой формы; поддерживающий код — полный серверный общий модуль УчебныеОбработчики85. Только новая CF УчебныеОповещенияСВ85, ровно два своих UUID-пользователя и два собственных сеанса на localhost:9094.
Подготовка
Нужна отдельная новая пустая CF УчебныеОповещенияСВ85 на8.5.1.1343: русский язык, управляемый интерфейс, совместимость Не использовать. Никаких предметных объектов, подписок, заданий, расширений, ботов и интеграций. Это другой стенд, не база урока одиночного пользователя.
Вручную разверните свой выделенный сервер взаимодействия в изолированной VM с адресом http://localhost:9094, отдельным собственным абонентом и приложением УчебныеОповещенияСВ85. Без SMTP, публичного доступа, общих приложений, внешних мессенджеров или чужих пользователей. Серверный конфигурационный контракт берется из поставки сервера, здесь не придумывается.
В конфигураторе вручную создайте ровно два своих пользователя: Уч85Получатель_<ваш новый канонический ненулевой UUID> и Уч85Отправитель_<другой ваш новый UUID>. Полные имена Учебный получатель/Учебный отправитель. UUID-маркер имени не является реальным IB UUID: реальные IB UUID код получает из фактических пользователей базы.
Назначьте необходимые права и включите систему взаимодействия для этих двух учебных учетных записей только вручную на своем стенде. Код не создает аккаунты/права. Двусторонний GetID/IBUUID и свежие internal-user паспорта должны совпасть; почта/телефон/внешние ID/ключи сопоставления пустые.
Войдите двумя отдельными сеансами этой своей базы: первый Получатель, второй Отправитель. Чужих сеансов нет. Оба ваши; разрешена отправка только между ними. Не используйте ВсеПользователиПриложения и не подменяйте ID строкой выдуманного UUID.
Создайте серверный общий модуль УчебныеОбработчики85 с флагом Сервер и выключенным Привилегированный; вставьте поддерживающий модуль целиком. Главный код вставьте целиком в модуль собственной внешней обработки/управляемой формы. Серверные функции формы вызывают этот модуль.
Добавьте строковые реквизиты формы Подтверждение длиной100, СвойКлючОтправителя длиной100, ЭтапОтправителя длиной30, ПодтверждениеОтправителя длиной100. Добавьте команды ПодготовитьПолучателя, ПодключитьЖдать, ОтключитьЖдать, ПодключитьCallback, ОтключитьCallback, ПроверитьСохранение, ОтправитьИзВторогоСеанса и одноименные обработчики.
В первом сеансе введите ДВА СВОИХ УЧАСТНИКА и нажмите ПодготовитьПолучателя один раз. Полученный собственный новый ключ скопируйте во второй сеанс в СвойКлючОтправителя. Во втором поле ПодтверждениеОтправителя задайте ОТПРАВИТЬ СВОЕМУ ПОЛУЧАТЕЛЮ. Без этих фраз записи нет.
Нажмите ПодключитьЖдать в первом сеансе и дождитесь сообщения о завершении. Только затем во втором выберите ЭтапОтправителя=Ждать и нажмите ОтправитьИзВторогоСеанса один раз. Дождитесь собственного нового события в первом сеансе; не открывайте обсуждение до этого, чтобы UI не изменил статус прочтения.
Нативные новые-message handlers работают thin/web/thick; серверная подготовка остается на сервере. Пример не использует противоречивый ПолучитьОбсуждениеАсинх и не требует переносить серверные IB-проверки на клиент. При необходимости одинаково проверяйте маршрут сначала в толстом управляемом клиенте.
Интерактивную проверку выполняйте вручную в первом сеансе: в интерфейсе системы взаимодействия найдите только собственное обсуждение по выданному ключу/заголовку и отправьте точный текст <ключ>|Ручной|750. Программный Записать второго сеанса не обязан вызывать AfterMessageSend: native указывает именно интерактивную отправку.
Перед закрытием формы сознательно нажмите соответствующее Отключить и дождитесь завершения. Автоматического подключения при открытии, отключения внутри события, повторов после таймаута и удаления сообщений нет. При неопределенном состоянии прекратите опыт и проверьте свой сеанс/сервер вручную; не считайте callback доказательством серверного отключения.
Перем СвойПакет85;
Перем НачатаПодготовка85;
Перем Режим85;
Перем Запрос85;
Перем СвойОбработчик85;
Перем ПослеОтправки85;
Перем Полученные85;
Перем Интерактивные85;
Перем Поздние85;
&НаКлиенте
Процедура ПодготовитьПолучателя(Команда)
Если НачатаПодготовка85 = Истина Тогда
ВызватьИсключение "Повтор запрещен: проверьте возможный частичный результат вручную";
КонецЕсли;
НачатаПодготовка85 = Истина;
СвойПакет85 = ПодготовитьНаСервере85();
Полученные85 = Новый Массив;
Интерактивные85 = 0;
Поздние85 = 0;
Режим85 = "Отключено";
Сообщить("Второму собственному сеансу передайте только этот ключ: " + СвойПакет85.Ключ);
КонецПроцедуры
&НаСервере
Функция ПодготовитьНаСервере85()
Возврат УчебныеОбработчики85.ПодготовитьСвоеОбсуждение85(Подтверждение);
КонецФункции
&НаКлиенте
Асинх Процедура ПодключитьЖдать(Команда)
ПроверитьСвоегоПолучателя85("Отключено");
ПодготовитьСвоиОписания85();
Режим85 = "ПодключаетсяЖдать";
Попытка
Результат = Ждать СистемаВзаимодействия.ПодключитьОбработчикНовыхСообщенийАсинх(СвойПакет85.Ключ, СвойОбработчик85, Неопределено);
Если Результат <> Неопределено Тогда
ВызватьИсключение "Native результат обещания подключения должен быть Неопределено";
КонецЕсли;
СистемаВзаимодействия.ПодключитьОбработчикПослеОтправкиСообщения(ПослеОтправки85);
Режим85 = "Ждать";
Исключение
Режим85 = "НеопределенноеСостояниеЖдать";
ВызватьИсключение;
КонецПопытки;
Сообщить("Клиентское подключение завершилось. Теперь отправьте этап Ждать из второго своего сеанса");
КонецПроцедуры
&НаКлиенте
Асинх Процедура ОтключитьЖдать(Команда)
ПроверитьСвоегоПолучателя85("Ждать");
Режим85 = "ОтключаетсяЖдать";
Попытка
Результат = Ждать СистемаВзаимодействия.ОтключитьОбработчикНовыхСообщенийАсинх(СвойПакет85.Ключ, СвойОбработчик85);
Если Результат <> Неопределено Тогда
ВызватьИсключение "Native результат обещания отключения должен быть Неопределено";
КонецЕсли;
СистемаВзаимодействия.ОтключитьОбработчикПослеОтправкиСообщения(ПослеОтправки85);
Режим85 = "Отключено";
Исключение
Режим85 = "НеопределенноеСостояниеЖдать";
ВызватьИсключение;
КонецПопытки;
Сообщить("Клиентская операция отключения завершилась; это не измерение подтверждения сервера");
КонецПроцедуры
&НаКлиенте
Процедура ПодключитьCallback(Команда)
ПроверитьСвоегоПолучателя85("Отключено");
ПодготовитьСвоиОписания85();
Завершение = Новый ОписаниеОповещения("ПодключениеЗавершено85", ЭтотОбъект, ПараметрыСвоегоЗапроса85(), "ОшибкаСвоегоОбработчика85", ЭтотОбъект);
Режим85 = "ПодключаетсяCallback";
Попытка
СистемаВзаимодействия.НачатьПодключениеОбработчикаНовыхСообщений(Завершение, СвойПакет85.Ключ, СвойОбработчик85, Неопределено);
Исключение
Режим85 = "НеопределенноеСостояниеCallback";
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
&НаКлиенте
Процедура ПодключениеЗавершено85(ДополнительныеПараметры) Экспорт
Если Не СвойТекущийЗапрос85(ДополнительныеПараметры) Или Режим85 <> "ПодключаетсяCallback" Тогда
Возврат;
КонецЕсли;
Попытка
СистемаВзаимодействия.ПодключитьОбработчикПослеОтправкиСообщения(ПослеОтправки85);
Режим85 = "Callback";
Исключение
Режим85 = "НеопределенноеСостояниеCallback";
ВызватьИсключение;
КонецПопытки;
Сообщить("Callback подключения получен. Теперь отправьте этап Callback из второго собственного сеанса");
КонецПроцедуры
&НаКлиенте
Процедура ОтключитьCallback(Команда)
ПроверитьСвоегоПолучателя85("Callback");
Завершение = Новый ОписаниеОповещения("ОтключениеЗавершено85", ЭтотОбъект, ПараметрыСвоегоЗапроса85(), "ОшибкаСвоегоОбработчика85", ЭтотОбъект);
Режим85 = "ОтключаетсяCallback";
Попытка
СистемаВзаимодействия.НачатьОтключениеОбработчикаНовыхСообщений(Завершение, СвойПакет85.Ключ, СвойОбработчик85);
Исключение
Режим85 = "НеопределенноеСостояниеCallback";
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
&НаКлиенте
Процедура ОтключениеЗавершено85(ДополнительныеПараметры) Экспорт
Если Не СвойТекущийЗапрос85(ДополнительныеПараметры) Или Режим85 <> "ОтключаетсяCallback" Тогда
Возврат;
КонецЕсли;
Попытка
СистемаВзаимодействия.ОтключитьОбработчикПослеОтправкиСообщения(ПослеОтправки85);
Режим85 = "Отключено";
Исключение
Режим85 = "НеопределенноеСостояниеCallback";
ВызватьИсключение;
КонецПопытки;
Сообщить("Callback отключения завершен. Native-справка прямо не гарантирует отключение обработчика сервером");
КонецПроцедуры
&НаКлиенте
Процедура СвоеНовоеСообщение85(Сообщение, ДополнительныеПараметры) Экспорт
Если Не СвойТекущийЗапрос85(ДополнительныеПараметры) Тогда
Возврат;
КонецЕсли;
Если ТипЗнч(Сообщение) <> Тип("СообщениеСистемыВзаимодействия") Тогда
Возврат;
КонецЕсли;
Если Строка(Сообщение.Обсуждение) <> СвойПакет85.Обсуждение
Или Строка(Сообщение.Автор) <> СвойПакет85.Отправитель Тогда
Возврат;
КонецЕсли;
Если Режим85 <> "Ждать" И Режим85 <> "Callback" Тогда
Поздние85 = Поздние85 + 1;
Сообщить("Своё событие пришло во время перехода или после отключения: считаем состояние неопределенным");
Возврат;
КонецЕсли;
Если Сообщение.Текст <> СвойПакет85.Ключ + "|" + Режим85 + "|Книга|3|250|750"
Или Сообщение.Данные <> СвойПакет85.Ключ + "|" + Режим85 + "|750" Тогда
Возврат;
КонецЕсли;
ИД = Строка(Сообщение.Идентификатор);
Если Полученные85.Найти(ИД) = Неопределено Тогда
Полученные85.Добавить(ИД);
Сообщить("Получена собственная копия нового сообщения: " + Режим85 + ", сумма750. Уникальных ID: " + Строка(Полученные85.Количество()));
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ПослеСвоейИнтерактивнойОтправки85(Сообщение, Обсуждение, ДополнительныеПараметры) Экспорт
Если Не СвойТекущийЗапрос85(ДополнительныеПараметры) Тогда
Возврат;
КонецЕсли;
Если ТипЗнч(Сообщение) <> Тип("СообщениеСистемыВзаимодействия")
Или ТипЗнч(Обсуждение) <> Тип("ОбсуждениеСистемыВзаимодействия") Тогда
Возврат;
КонецЕсли;
Если Строка(Обсуждение.Идентификатор) <> СвойПакет85.Обсуждение
Или Строка(Сообщение.Обсуждение) <> СвойПакет85.Обсуждение
Или Строка(Сообщение.Автор) <> СвойПакет85.Получатель Тогда
Возврат;
КонецЕсли;
Если Режим85 <> "Ждать" И Режим85 <> "Callback" Тогда
Возврат;
КонецЕсли;
Если Обсуждение.Ключ <> СвойПакет85.Ключ Или Сообщение.Текст <> СвойПакет85.Ключ + "|Ручной|750" Тогда
Возврат;
КонецЕсли;
Интерактивные85 = Интерактивные85 + 1;
Сообщить("Замечена своя интерактивная отправка. Проверяйте сохранение отдельно кнопкой ПроверитьСохранение");
КонецПроцедуры
&НаКлиенте
Процедура ОшибкаСвоегоОбработчика85(ИнформацияОбОшибке, ДополнительныеПараметры) Экспорт
Если СвойТекущийЗапрос85(ДополнительныеПараметры) Тогда
Если Режим85 = "Ждать" Или Режим85 = "ОтключаетсяЖдать" Тогда
Режим85 = "НеопределенноеСостояниеЖдать";
Иначе
Режим85 = "НеопределенноеСостояниеCallback";
КонецЕсли;
Сообщить("Ошибка своего обработчика: состояние не подтверждено, сырые ошибки не выводятся, повторов нет");
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ПроверитьСохранение(Команда)
Если СвойПакет85 = Неопределено Тогда
ВызватьИсключение "Собственное обсуждение не подготовлено";
КонецЕсли;
Факт = ПроверитьНаСервере85(СвойПакет85.Ключ);
Сообщить("Серверное сохранение: Ждать=" + Строка(Факт.Ожидание) + ", Callback=" + Строка(Факт.Callback)
+ ", ПослеОтключения=" + Строка(Факт.ПослеОтключения) + ", Ручной=" + Строка(Факт.Ручной));
Сообщить("Клиентские наблюдения: уникальных собственных новых ID=" + Строка(Полученные85.Количество())
+ ", собственных интерактивных отправок=" + Строка(Интерактивные85) + ", событий вне активного режима=" + Строка(Поздние85));
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПроверитьНаСервере85(Ключ)
Возврат УчебныеОбработчики85.ПроверитьСохраненныеСообщения85(Ключ);
КонецФункции
&НаКлиенте
Процедура ОтправитьИзВторогоСеанса(Команда)
ОтправитьНаСервере85(СвойКлючОтправителя, ЭтапОтправителя, ПодтверждениеОтправителя);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ОтправитьНаСервере85(Ключ, Этап, Фраза)
УчебныеОбработчики85.ОтправитьСвоеСообщение85(Ключ, Этап, Фраза);
КонецПроцедуры
&НаКлиенте
Процедура ПроверитьСвоегоПолучателя85(ОжидаемыйРежим)
Если СвойПакет85 = Неопределено Или Режим85 <> ОжидаемыйРежим Тогда
ВызватьИсключение "Неверный этап: не подключайте и не отключайте обработчик повторно";
КонецЕсли;
Если Не СистемаВзаимодействия.ИнформационнаяБазаЗарегистрирована()
Или СистемаВзаимодействия.АдресСервера() <> "http://localhost:9094"
Или Строка(СистемаВзаимодействия.ИдентификаторТекущегоПользователя()) <> СвойПакет85.Получатель Тогда
ВызватьИсключение "Свой сервер или получатель изменен";
КонецЕсли;
ПроверитьНаСервере85(СвойПакет85.Ключ);
КонецПроцедуры
&НаКлиенте
Процедура ПодготовитьСвоиОписания85()
Запрос85 = Строка(Новый УникальныйИдентификатор);
СвойОбработчик85 = Новый ОписаниеОповещения("СвоеНовоеСообщение85", ЭтотОбъект, ПараметрыСвоегоЗапроса85(), "ОшибкаСвоегоОбработчика85", ЭтотОбъект);
ПослеОтправки85 = Новый ОписаниеОповещения("ПослеСвоейИнтерактивнойОтправки85", ЭтотОбъект, ПараметрыСвоегоЗапроса85(), "ОшибкаСвоегоОбработчика85", ЭтотОбъект);
КонецПроцедуры
&НаКлиенте
Функция ПараметрыСвоегоЗапроса85()
Возврат Новый Структура("Запрос,Форма,Ключ", Запрос85, Строка(УникальныйИдентификатор), СвойПакет85.Ключ);
КонецФункции
&НаКлиенте
Функция СвойТекущийЗапрос85(Параметры)
Если СвойПакет85 = Неопределено Тогда
Возврат Ложь;
КонецЕсли;
Если Не СистемаВзаимодействия.ИнформационнаяБазаЗарегистрирована()
Или СистемаВзаимодействия.АдресСервера() <> "http://localhost:9094"
Или Строка(СистемаВзаимодействия.ИдентификаторТекущегоПользователя()) <> СвойПакет85.Получатель Тогда
Возврат Ложь;
КонецЕсли;
Возврат Параметры.Запрос = Запрос85 И Параметры.Форма = Строка(УникальныйИдентификатор)
И Параметры.Ключ = СвойПакет85.Ключ;
КонецФункцииПолный серверный общий модуль УчебныеОбработчики85: actual pair/own discussion/send/fresh reads
Собственная конфигурация → Общие → Общие модули → УчебныеОбработчики85.
Серверный общий модуль своей отдельной конфигурации УчебныеОповещенияСВ85; не переносить на клиент.
Функция ПодготовитьСвоеОбсуждение85(Подтверждение) Экспорт
Пара = ПроверитьСвоюПару85("Получатель");
Если Подтверждение <> "ДВА СВОИХ УЧАСТНИКА" Тогда
ВызватьИсключение "Прочитайте подготовку; обсуждение не создано";
КонецЕсли;
Ключ = "Уч85Оповещения_" + Строка(Новый УникальныйИдентификатор);
Сообщить("Свой новый ключ для второго сеанса: " + Ключ);
Если СистемаВзаимодействия.ПолучитьОбсуждение(Ключ) <> Неопределено Тогда
ВызватьИсключение "Ключ занят; чужое обсуждение не используется";
КонецЕсли;
Обсуждение = СистемаВзаимодействия.СоздатьОбсуждение();
Обсуждение.Ключ = Ключ;
Обсуждение.Заголовок = Ключ + " | Проверка уведомлений750";
Обсуждение.Групповое = Истина;
Обсуждение.Отображаемое = Истина;
Обсуждение.ТолькоПросмотр = Ложь;
Обсуждение.КонтекстОбсуждения = Неопределено;
Обсуждение.Интеграция = Неопределено;
Обсуждение.ВнешнийИдентификатор = "";
Обсуждение.Участники.Добавить(Пара.Получатель);
Обсуждение.Участники.Добавить(Пара.Отправитель);
Обсуждение.Записать();
Факт = ПроверитьСвоеОбсуждение85(Ключ, Пара);
Если Факт.Идентификатор <> Обсуждение.Идентификатор Тогда
ВызватьИсключение "Полученный собственный ID обсуждения не совпал";
КонецЕсли;
Возврат Новый Структура("Ключ,Обсуждение,Получатель,Отправитель", Ключ, Строка(Факт.Идентификатор), Строка(Пара.Получатель), Строка(Пара.Отправитель));
КонецФункции
Процедура ОтправитьСвоеСообщение85(Ключ, Этап, Подтверждение) Экспорт
Пара = ПроверитьСвоюПару85("Отправитель");
Если Подтверждение <> "ОТПРАВИТЬ СВОЕМУ ПОЛУЧАТЕЛЮ"
Или (Этап <> "Ждать" И Этап <> "Callback" И Этап <> "ПослеОтключения") Тогда
ВызватьИсключение "Подтверждение или этап неверны. Сообщение не отправлено";
КонецЕсли;
Обсуждение = ПроверитьСвоеОбсуждение85(Ключ, Пара);
Сообщение = СистемаВзаимодействия.СоздатьСообщение(Обсуждение.Идентификатор);
Сообщение.Текст = Ключ + "|" + Этап + "|Книга|3|250|750";
Сообщение.Данные = Ключ + "|" + Этап + "|750";
Сообщение.ФорматДанныхСтроки = "text/plain";
Сообщение.Получатели.Добавить(Пара.Получатель);
Если Сообщение.Получатели.Количество() <> 1 Или Не Сообщение.Получатели.Содержит(Пара.Получатель)
Или Сообщение.Вложения.Количество() <> 0 Или Сообщение.Действия.Количество() <> 0
Или Сообщение.ПанельКнопок.РядыКнопок.Количество() <> 0 Тогда
ВызватьИсключение "Состав собственной отправки неверен";
КонецЕсли;
ПроверитьСвоеОбсуждение85(Ключ, Пара);
Сообщение.Записать();
ИД = Сообщение.Идентификатор;
Если ТипЗнч(ИД) <> Тип("ИдентификаторСообщенияСистемыВзаимодействия") Тогда
ВызватьИсключение "Запись могла произойти, но ID не получен";
КонецЕсли;
Факт = СистемаВзаимодействия.ПолучитьСообщение(ИД);
Если Факт.Идентификатор <> ИД Или Факт.Обсуждение <> Обсуждение.Идентификатор
Или Факт.Автор <> Пара.Отправитель Или Факт.Текст <> Сообщение.Текст Или Факт.Данные <> Сообщение.Данные Тогда
ВызватьИсключение "Собственная отправка отдельно не подтверждена";
КонецЕсли;
Сообщить("Сохранение собственного сообщения отдельно подтверждено; это еще не доказательство доставки callback получателю");
КонецПроцедуры
Функция ПроверитьСохраненныеСообщения85(Ключ) Экспорт
Пара = ПроверитьСвоюПару85("Получатель");
Обсуждение = ПроверитьСвоеОбсуждение85(Ключ, Пара);
Отбор = Новый ОтборСообщенийСистемыВзаимодействия;
Отбор.Обсуждение = Обсуждение.Идентификатор;
Отбор.Количество = 50;
Отбор.НаправлениеСортировки = НаправлениеСортировки.Возр;
Сообщения = СистемаВзаимодействия.ПолучитьСообщения(Отбор);
Если Сообщения.Количество() >= 50 Тогда
ВызватьИсключение "Учебное обсуждение превысило лимит50; нужен новый свой опыт";
КонецЕсли;
Счетчики = Новый Структура("Ожидание,Callback,ПослеОтключения,Ручной", 0, 0, 0, 0);
Для Каждого Сообщение Из Сообщения Цикл
Если Сообщение.Обсуждение <> Обсуждение.Идентификатор Тогда
ВызватьИсключение "Список содержит другое обсуждение";
КонецЕсли;
Для Каждого Счетчик Из Счетчики Цикл
Этап = Счетчик.Ключ;
Если Этап = "Ожидание" Тогда
Этап = "Ждать";
КонецЕсли;
Если Счетчик.Ключ = "Ручной" Тогда
Совпало = Сообщение.Автор = Пара.Получатель И Сообщение.Текст = Ключ + "|Ручной|750";
Иначе
Совпало = Сообщение.Автор = Пара.Отправитель
И Сообщение.Текст = Ключ + "|" + Этап + "|Книга|3|250|750"
И Сообщение.Данные = Ключ + "|" + Этап + "|750";
КонецЕсли;
Если Совпало Тогда
Счетчики[Счетчик.Ключ] = Счетчики[Счетчик.Ключ] + 1;
КонецЕсли;
КонецЦикла;
КонецЦикла;
Возврат Счетчики;
КонецФункции
Функция ПроверитьСвоеОбсуждение85(Ключ, Пара)
Если ТипЗнч(Ключ) <> Тип("Строка") Или Лев(Ключ, СтрДлина("Уч85Оповещения_")) <> "Уч85Оповещения_" Тогда
ВызватьИсключение "Используйте только ключ нового собственного обсуждения";
КонецЕсли;
UUIDКлюча = Новый УникальныйИдентификатор(Сред(Ключ, СтрДлина("Уч85Оповещения_") + 1));
Если Ключ <> "Уч85Оповещения_" + Строка(UUIDКлюча)
Или UUIDКлюча = Новый УникальныйИдентификатор("00000000-0000-0000-0000-000000000000") Тогда
ВызватьИсключение "Ключ должен содержать полученный собственный канонический UUID";
КонецЕсли;
Факт = СистемаВзаимодействия.ПолучитьОбсуждение(Ключ);
Если Факт = Неопределено Тогда
ВызватьИсключение "Собственное обсуждение не найдено. Никакие ID не выдумываются";
КонецЕсли;
Если Факт.Ключ <> Ключ Или Факт.Заголовок <> Ключ + " | Проверка уведомлений750"
Или Не Факт.Групповое Или Не Факт.Отображаемое Или Факт.ТолькоПросмотр
Или Факт.КонтекстОбсуждения <> Неопределено Или Факт.Интеграция <> Неопределено
Или Факт.ВнешнийИдентификатор <> "" Или Факт.Участники.Количество() <> 2
Или Не Факт.Участники.Содержит(Пара.Получатель) Или Не Факт.Участники.Содержит(Пара.Отправитель) Тогда
ВызватьИсключение "Паспорт обсуждения двух собственных участников изменен";
КонецЕсли;
Возврат Факт;
КонецФункции
Функция ПроверитьСвоюПару85(Роль)
ПроверитьСвойПустойСтенд();
Если ПривилегированныйРежим() Или (Роль <> "Получатель" И Роль <> "Отправитель") Тогда
ВызватьИсключение "Роль или привилегированный режим неверны";
КонецЕсли;
Если Не СистемаВзаимодействия.ИнформационнаяБазаЗарегистрирована()
Или Не СистемаВзаимодействия.ИспользованиеДоступно()
Или СистемаВзаимодействия.АдресСервера() <> "http://localhost:9094" Тогда
ВызватьИсключение "Нужно собственное изолированное приложение на localhost:9094";
КонецЕсли;
Приложение = СистемаВзаимодействия.ПолучитьПриложение(СистемаВзаимодействия.ИдентификаторТекущегоПриложения());
Если Приложение.Идентификатор <> СистемаВзаимодействия.ИдентификаторТекущегоПриложения()
Или Приложение.Наименование <> "УчебныеОповещенияСВ85" Тогда
ВызватьИсключение "Наименование своего нового приложения не совпало";
КонецЕсли;
Пользователи = ПользователиИнформационнойБазы.ПолучитьПользователей();
Текущий = ПользователиИнформационнойБазы.ТекущийПользователь();
Если Пользователи.Количество() <> 2 Или Текущий = Неопределено Тогда
ВызватьИсключение "Нужны ровно две собственные UUID-учетные записи";
КонецЕсли;
Пара = Новый Структура("Получатель,Отправитель", Неопределено, Неопределено);
ТекущийПодтвержден = Ложь;
Для Каждого ПользовательБазы Из Пользователи Цикл
Если Лев(ПользовательБазы.Имя, СтрДлина("Уч85Получатель_")) = "Уч85Получатель_" Тогда
ИмяРоли = "Получатель";
ИначеЕсли Лев(ПользовательБазы.Имя, СтрДлина("Уч85Отправитель_")) = "Уч85Отправитель_" Тогда
ИмяРоли = "Отправитель";
Иначе
ВызватьИсключение "Посторонняя учетная запись недопустима";
КонецЕсли;
Префикс = "Уч85" + ИмяРоли + "_";
UUIDИмени = Новый УникальныйИдентификатор(Сред(ПользовательБазы.Имя, СтрДлина(Префикс) + 1));
Если ПользовательБазы.Имя <> Префикс + Строка(UUIDИмени)
Или UUIDИмени = Новый УникальныйИдентификатор("00000000-0000-0000-0000-000000000000") Тогда
ВызватьИсключение "Имя своего участника должно содержать канонический ненулевой UUID";
КонецЕсли;
ИД = СистемаВзаимодействия.ПолучитьИдентификаторПользователя(ПользовательБазы.УникальныйИдентификатор);
Если ТипЗнч(ИД) <> Тип("ИдентификаторПользователяСистемыВзаимодействия")
Или ИД = Новый ИдентификаторПользователяСистемыВзаимодействия
Или ИД = СистемаВзаимодействия.СтандартныеПользователи.ВсеПользователиПриложения
Или СистемаВзаимодействия.ПолучитьИдентификаторПользователяИнформационнойБазы(ИД) <> ПользовательБазы.УникальныйИдентификатор Тогда
ВызватьИсключение "Реальная связка своего пользователя с базой не подтверждена";
КонецЕсли;
Пользователь = СистемаВзаимодействия.ПолучитьПользователя(ИД);
Если Пользователь.Идентификатор <> ИД Или Пользователь.Имя <> ПользовательБазы.Имя
Или Пользователь.ПолноеИмя <> ПользовательБазы.ПолноеИмя
Или Пользователь.ИдентификаторПользователяИнформационнойБазы <> ПользовательБазы.УникальныйИдентификатор
Или Пользователь.Внешний Или Пользователь.Заблокирован Или Пользователь.ТипВнешнейСистемы <> ""
Или Пользователь.ИдентификаторПользователяВнешнейСистемы <> Неопределено
Или Пользователь.АдресЭлектроннойПочты <> "" Или Пользователь.АдресЭлектроннойПочтыДляАутентификации <> ""
Или Пользователь.НомерТелефона <> "" Или Пользователь.КлючСопоставления <> "" Тогда
ВызватьИсключение "Нужен собственный внутренний учебный паспорт без персональных контактов";
КонецЕсли;
Если Пара[ИмяРоли] <> Неопределено Тогда
ВызватьИсключение "Роль продублирована";
КонецЕсли;
Пара[ИмяРоли] = ИД;
Если ПользовательБазы.УникальныйИдентификатор = Текущий.УникальныйИдентификатор Тогда
Если ИмяРоли <> Роль Или ИД <> СистемаВзаимодействия.ИдентификаторТекущегоПользователя() Тогда
ВызватьИсключение "Войдите под своей учетной записью нужной роли";
КонецЕсли;
ТекущийПодтвержден = Истина;
КонецЕсли;
КонецЦикла;
Если Не ТекущийПодтвержден Или Пара.Получатель = Неопределено Или Пара.Отправитель = Неопределено
Или Пара.Получатель = Пара.Отправитель Тогда
ВызватьИсключение "Не получены два разных реальных собственных ID";
КонецЕсли;
Возврат Пара;
КонецФункции
Процедура ПроверитьСвойПустойСтенд()
Если Метаданные.Справочники.Количество() <> 0 Или Метаданные.Документы.Количество() <> 0 Или Метаданные.Перечисления.Количество() <> 0 Или Метаданные.РегистрыСведений.Количество() <> 0 Или Метаданные.РегистрыНакопления.Количество() <> 0 Или Метаданные.РегистрыБухгалтерии.Количество() <> 0 Или Метаданные.РегистрыРасчета.Количество() <> 0 Или Метаданные.ПланыВидовХарактеристик.Количество() <> 0 Или Метаданные.ПланыВидовРасчета.Количество() <> 0 Или Метаданные.ПланыСчетов.Количество() <> 0 Или Метаданные.ПланыОбмена.Количество() <> 0 Или Метаданные.Последовательности.Количество() <> 0 Или Метаданные.БизнесПроцессы.Количество() <> 0 Или Метаданные.Задачи.Количество() <> 0 Или Метаданные.ОбщиеРеквизиты.Количество() <> 0 Или Метаданные.ПодпискиНаСобытия.Количество() <> 0 Или Метаданные.РегламентныеЗадания.Количество() <> 0 Или Метаданные.ВнешниеИсточникиДанных.Количество() <> 0 Тогда
ВызватьИсключение "Нужен только свой новый пустой учебный стенд";
КонецЕсли;
Если Метаданные.ЕстьИзмененияРасширениямиКонфигурации() Тогда
ВызватьИсключение "Свой сеанс не должен изменяться расширениями";
КонецЕсли;
Источники = Новый Массив;
Источники.Добавить(ИсточникРасширенийКонфигурации.БазаДанных);
Источники.Добавить(ИсточникРасширенийКонфигурации.СеансАктивные);
Источники.Добавить(ИсточникРасширенийКонфигурации.СеансОтключенные);
Для Каждого Источник Из Источники Цикл
Расширения = РасширенияКонфигурации.Получить(, Источник);
Если ТипЗнч(Расширения) <> Тип("Массив") Тогда
ВызватьИсключение "Native список расширений должен иметь тип Массив";
КонецЕсли;
Если Расширения.Количество() <> 0 Тогда
ВызватьИсключение "В своём новом стенде нет расширений базы или сеанса";
КонецЕсли;
КонецЦикла;
Если ТранзакцияАктивна() Тогда
ВызватьИсключение "Чужая или вложенная активная транзакция не допускается";
КонецЕсли;
КонецПроцедурыРазбор по шагам
Проверка своей среды отвергает третью учетную запись, одинаковые роли, пустые/стандартные ID, внешние контакты, чужое приложение и адрес не localhost:9094. Реальные идентификаторы берутся только из двух реальных пользователей собственной базы.
Получатель создает новый UUID-ключ и собственное отображаемое неконтекстное обсуждение ровно двух подтвержденных участников. Перед каждым серверным чтением/отправкой свежая копия проверяет exact key/title/состав/интеграцию/контекст.
Для клиентской подписки создаются два разных ОписанияОповещения: новое сообщение и после интерактивной отправки. ДополнительныеПараметры содержит request UUID, form UUID и свой ключ; поздние события предыдущего опыта отбрасываются.
ПодключитьЖдать ожидает обещание с результатом Неопределено, а не булево/объект сообщения. Только после завершения разрешен этап Ждать второго сеанса. СтатусОповещенияПользователя=Неопределено означает прямой вызов обработчика, не кликабельный popup.
Отправитель проверяет своего текущего пользователя и обоих собственных участников заново. Новое сообщение содержит книгу3×250=750 и UUID-ключ, адресат ровно собственный получатель. Дата и Автор не устанавливаются привилегированно.
После Записать отправитель получает настоящий ID и читает серверный результат отдельно. Это подтверждает сохранение, но не доставку клиентского события. Получатель проверяет тип/обсуждение/автора до чтения текста копии, затем exact этап/данные и уникальность real ID.
ОтключитьЖдать использует тот же сохраненный объект ОписаниеОповещения, а не отключение всех чужих обработчиков. Ждать возвращает Неопределено. AfterMessageSend отключается отдельно. Отключение никогда не выполняется внутри обработчика сообщения.
ПодключитьCallback начинает другой собственный request UUID. Завершение подключения получает ровно один параметр ДополнительныеПараметры; поток/булево/результат сюда не добавляются. После этого второй сеанс отправляет только этап Callback.
ОтключитьCallback передает тот же зарегистрированный handler. Подпись завершения снова один параметр. Native-справка прямо говорит: этот callback вызывается как успешный даже если handler ранее не был подключен или сервер не выполнил отключение.
Ручная отправка <ключ>|Ручной|750 в первом сеансе дает отдельное наблюдение AfterMessageSend(Сообщение,Обсуждение,ДополнительныеПараметры). Фильтр глобального handler отбрасывает чужие обсуждения/авторов, ничего из них не журналирует и не меняет.
AfterMessageSend передает копию с локальной текущей датой и текущим автором, не доказательство серверной записи. Кнопка ПроверитьСохранение отдельно читает только свое подтвержденное обсуждение и считает точные сохраненные сообщения Ждать/Callback/ПослеОтключения/Ручной.
После отключения во втором сеансе можно один раз отправить этап ПослеОтключения. ПроверитьСохранение должен показать его серверное сохранение; клиентское наблюдение/отсутствие callback оценивайте отдельно. Отсутствие за некоторое время не является гарантией от сервера.
Сравнение handler native учитывает имя процедуры/модуль и обработчик ошибок/его модуль, но не ДополнительныеПараметры. Поэтому два API-стиля не подключены одновременно; переходы выполняются последовательно с одной сохраненной ссылкой handler.
Как проверить результат
В обоих сеансах проверены реальные разные UUID-linked внутренние учетные записи; роли перепутаны или third user → отказ до отправки.
Для своего этапа Ждать/Callback серверный счетчик >=1 и наблюдение уникального ID в правильном сеансе являются разными проверками. Поздние повторные копии с тем же ID не увеличивают количество уникальных новых сообщений.
Интерактивный счетчик растет только для точного собственного обсуждения и текста <ключ>|Ручной|750; программная отправка не используется как доказательство интерактивного события.
Все три штатных success callback имеют native arity: завершения1, new-message2, after-send3. Обещания подключения/отключения ожидаются и проверяются на Неопределено.
ПослеОтключения отдельно подтверждается по серверной записи. Программа не обещает, что completion callback измерил факт отключения на сервере; конфликт наблюдений считается неопределенным состоянием.
Пустая фраза, чужой ключ без собственного префикса, добавленный участник, внешний контекст/интеграция → отказ. Чужой текст/сырая ошибка/контакты никогда не попадают в сообщения формы.
Ожидаемый результат
При ручном запуске на своем двухпользовательском стенде получатель увидит собственные copy-events после реальных сообщений другого своего участника; серверные записи и интерактивная отправка проверяются отдельно. Оба API-стиля подписки используются полностью. Эти примеры статически сверены со справкой8.5.1.1343, реальные сеансы/отправка здесь не выполнялись.
На что обратить внимание
Не используйте fake UUID вместо actual ID: имя с UUID — только метка, связь берется из реального пользователя информационной базы и подтверждается в обе стороны.
Два сеанса одного пользователя не являются двумя участниками. Этот полный workflow требует именно двух разных собственных учетных записей; он не подходит для одиночного профиля предыдущего урока.
Подключение может сразу дать непрочитанные старые сообщения. Маркер/автор/этап/уникальный ID и request UUID отделяют такие события от новой проверки. Порядок нескольких handler не определен.
ДополнительныеПараметры не участвуют в равенстве handler; новый request UUID не создает независимый native-handler с той же процедурой/модулем.
В new-message передается копия. Не записывайте ее повторно, не ставьте Author/Date и не отключайте обработчик из его же тела; этот код только проверяет и считает собственные наблюдения.
AfterMessageSend относится к интерактивной отправке и использует локальную дату; его наличие/ID не считается подтверждением сохранения. Проверяйте отдельное серверное чтение.
Success callback BeginDetach вызывается даже без подтверждения действия сервером. Отсутствие наблюдения некоторое время не доказывает доставку/отключение: здесь не скрыта эта native граница.
Ключ и состав участников — прикладные страховки, не криптографическое доказательство владения сервером. Собственный абонент/отсутствие внешних связей/изолированная VM обязательны вручную.
Лимит50 — учебный предел; для больших обсуждений нужна настоящая пагинация. В этом уроке нельзя молча брать первые50 и выдавать неполный список как полный.
Не выполняйте автоматические повторы отправки или cleanup при ошибке. После закрытия формы нельзя считать клиентские callback/счетчики восстановленными; новые резервные копии не создаются.
Пример не запускался в вашей базе. Перед применением проверьте его в указанном учебном контексте.