From cc4e9c3db4e6febbc7c36906da125344bf60131b Mon Sep 17 00:00:00 2001 From: SergioSim Date: Thu, 12 Sep 2024 20:19:48 +0200 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=94=92=EF=B8=8F(project)=20move=20cop?= =?UTF-8?q?ilot=20token=20request=20to=20backend?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Copilot Endpoint URL is not a secret per se. However, we anticipate that in the future, we will use a Copilot that requires passing a secret in the Endpoint URL request's header. Thus, we no longer pass the Copilot Endpoint URL to the client JavaScript to avoid exposing this secret to Moodle users and make the request on the backend side instead. --- block_iagora.php | 45 ++++++++++++++++++++++++++++++++-------- lang/en/block_iagora.php | 1 + templates/chat.mustache | 30 +++++---------------------- 3 files changed, 42 insertions(+), 34 deletions(-) diff --git a/block_iagora.php b/block_iagora.php index e9e3d75..49a1703 100644 --- a/block_iagora.php +++ b/block_iagora.php @@ -42,36 +42,45 @@ public function init() { * @return string The block HTML. */ public function get_content() { - if ($this->content !== null) { + if (isset($this->content)) { return $this->content; } $this->content = new stdClass(); $this->content->footer = ''; - $copilotendpointurl = isset($this->config->copilotendpointurl) ? $this->config->copilotendpointurl : ''; + $copilotendpointurl = $this->config->copilotendpointurl; + $directlineurl = $this->config->directlineurl; - if (empty($copilotendpointurl)) { + if (!isset($copilotendpointurl)) { $this->content->text = get_string('nocopilotendpointurl', 'block_iagora'); - } else { - $this->content->text = $this->generate_chat_content($copilotendpointurl); + return $this->content; + } + + if(!isset($directlineurl)) { + $this->content->text = get_string('nodirectlineurl', 'block_iagora'); + return $this->content; } - return $this->content; + $token = json_decode(file_get_contents($copilotendpointurl), true)['token']; + $this->content->text = $this->generate_chat_content($directlineurl, $token); + return $this->content; } /** * Generate the HTML content for the chat block. * - * @param string $copilotendpointurl The URL to be used in the iframe. + * @param string $directlineurl The Direct Line API base URL. + * @param string $token The Copilot Direct Line token. * @return string The generated HTML content for the chat. */ - private function generate_chat_content($copilotendpointurl) { + private function generate_chat_content($directlineurl, $token) { global $OUTPUT; // Generate a unique identifier for the chat container. $chatid = uniqid('iagora_chat_'); $context = [ 'chatId' => $chatid, - 'tokenEndpointURL' => $copilotendpointurl, + 'directLineURL' => $directlineurl, + 'token' => $token, ]; return $OUTPUT->render_from_template('block_iagora/chat', $context); } @@ -104,7 +113,25 @@ public function instance_allow_config() { public function instance_config_save($data, $nolongerused = false) { if (isset($data->copilotendpointurl)) { $data->copilotendpointurl = clean_param($data->copilotendpointurl, PARAM_URL); + $directlineurl = $this->get_directline_url($data->copilotendpointurl); + $data->directlineurl = clean_param($directlineurl, PARAM_URL); } return parent::instance_config_save($data, $nolongerused); } + + /** + * Return the Direct Line API base URI from the Copilot token endpoint URL. + * + * @param string $copilotendpointurl The Copilot token endpoint URL. + * @return string The Direct Line API base URL. + */ + public function get_directline_url($copilotendpointurl) { + $copilotendpoint = parse_url($copilotendpointurl); + parse_str($copilotendpoint['query'], $query); + $base = "{$copilotendpoint['scheme']}://{$copilotendpoint['host']}"; + $path = "/powervirtualagents/regionalchannelsettings"; + $url = "{$base}{$path}?api-version={$query['api-version']}"; + $response = file_get_contents($url); + return json_decode($response, true)['channelUrlsById']['directline']; + } } diff --git a/lang/en/block_iagora.php b/lang/en/block_iagora.php index 6f87ac6..192c27a 100644 --- a/lang/en/block_iagora.php +++ b/lang/en/block_iagora.php @@ -26,4 +26,5 @@ $string['copilotendpointurl_desc'] = 'Microsoft Copilot Token Endpoint URL'; $string['copilotendpointurl_help'] = 'The Microsoft Copilot Token Endpoint URL is used to get the Direct Line token'; $string['nocopilotendpointurl'] = 'No Copilot Endpoint URL defined for this block. Please configure it in the block parameters'; +$string['nodirectlineurl'] = 'Failed to get Direct Line URL. Please check and update the Copilot Endpoint URL parameter'; $string['pluginname'] = 'IAGORA'; diff --git a/templates/chat.mustache b/templates/chat.mustache index 2197f4b..4b33be1 100644 --- a/templates/chat.mustache +++ b/templates/chat.mustache @@ -27,12 +27,14 @@ Context variables required for this template: * chatId: The ID of the chat div element where to render the chat block. - * tokenEndpointURL: The Copilot Token Endpoint URL for native apps. + * directLineURL: The Copilot Direct Line API base URL. + * token: The Copilot Direct Line token. Example context (json): { "chatId": "iagora_chat_55a6cad25a0f6", - "tokenEndpointURL": "https://insert/your/copilot/endpoint/url" + "directLineURL": "https://directline.botframework.com", + "token": "RCurR_XV9ZA.cwA.BKA.iaJrC8xpy8qbOF5xnR..." } }} {{#pix}} t/message, core {{/pix}} @@ -47,30 +49,8 @@ const styleOptions = { hideUploadButton: true }; - const tokenEndpointURL = new URL('{{tokenEndpointURL}}'); const locale = document.documentElement.lang || 'en'; - const apiVersion = tokenEndpointURL.searchParams.get('api-version'); - - const [directLineURL, token] = await Promise.all([ - fetch(new URL(`/powervirtualagents/regionalchannelsettings?api-version=${apiVersion}`, tokenEndpointURL)) - .then(response => { - if (!response.ok) { - throw new Error('Failed to retrieve regional channel settings.'); - } - return response.json(); - }) - .then(({channelUrlsById: {directline}}) => directline), - fetch(tokenEndpointURL) - .then(response => { - if (!response.ok) { - throw new Error('Failed to retrieve Direct Line token.'); - } - return response.json(); - }) - .then(({token}) => token) - ]); - - const directLine = WebChat.createDirectLine({domain: new URL('v3/directline', directLineURL), token}); + const directLine = WebChat.createDirectLine({domain: new URL('v3/directline', '{{directLineURL}}'), token: '{{token}}'}); const subscription = directLine.connectionStatus$.subscribe({ next(value) { From d0417efe6c976a0dad2d0466e7b857955560bf67 Mon Sep 17 00:00:00 2001 From: SergioSim Date: Fri, 13 Sep 2024 09:53:23 +0200 Subject: [PATCH 2/2] WIP --- block_iagora.php | 10 +++++++++- templates/chat.mustache | 13 ++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/block_iagora.php b/block_iagora.php index 49a1703..d65bbdc 100644 --- a/block_iagora.php +++ b/block_iagora.php @@ -74,12 +74,20 @@ public function get_content() { * @return string The generated HTML content for the chat. */ private function generate_chat_content($directlineurl, $token) { - global $OUTPUT; + global $OUTPUT, $USER, $COURSE, $PAGE; // Generate a unique identifier for the chat container. + // die(var_dump($PAGE)); $chatid = uniqid('iagora_chat_'); + $info = get_fast_modinfo($COURSE); $context = [ 'chatId' => $chatid, 'directLineURL' => $directlineurl, + 'moodleActivityContent' => $PAGE->activityrecord->content, + 'moodleActivityName' => $PAGE->activityrecord->name, + 'moodleActivityType' => $PAGE->pagetype, + 'moodleCourseName' => $COURSE->fullname, + 'moodleSectionName' =>$info->get_section_info($PAGE->cm->sectionnum)->name, + 'moodleUserName' => $USER->firstname, 'token' => $token, ]; return $OUTPUT->render_from_template('block_iagora/chat', $context); diff --git a/templates/chat.mustache b/templates/chat.mustache index 4b33be1..69104ee 100644 --- a/templates/chat.mustache +++ b/templates/chat.mustache @@ -37,8 +37,7 @@ "token": "RCurR_XV9ZA.cwA.BKA.iaJrC8xpy8qbOF5xnR..." } }} -{{#pix}} t/message, core {{/pix}} -
+
@@ -60,7 +59,15 @@ localTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone, locale, name: 'startConversation', - type: 'event' + type: 'event', + value: { + 'MoodleActivityContent': '{{moodleActivityContent}}', + 'MoodleActivityName': '{{moodleActivityName}}', + 'MoodleActivityType': '{{moodleActivityType}}', + 'MoodleCourseName': '{{moodleCourseName}}', + 'MoodleSectionName': '{{moodleSectionName}}', + 'MoodleUserName': '{{moodleUserName}}', + } }) .subscribe(); subscription.unsubscribe();