|
4 | 4 |
|
5 | 5 | This document describes how to embed the XtremeIdiots Portal notification widget on the xtremeidiots.com Invision Community forum. The widget shows personalised notifications for logged-in forum users and a public activity feed for guests. |
6 | 6 |
|
7 | | -## Prerequisites |
| 7 | +## Installation (2 steps) |
8 | 8 |
|
9 | | -1. The HMAC shared secret must be configured in both: |
10 | | - - **Portal**: via Azure App Configuration key `XtremeIdiots:ExternalWidget:HmacSecret` (auto-provisioned by portal-environments Terraform) |
11 | | - - **Forum**: as a custom Invision Community setting (see step 2 below) |
12 | | - |
13 | | -2. The portal must be deployed with the external notifications API (`/api/external/notifications`) |
14 | | - |
15 | | -## Step 1: Add the HMAC Secret to Invision Community |
16 | | - |
17 | | -1. Go to **ACP → System → Settings** (or use a custom plugin settings page) |
18 | | -2. Add a custom setting: |
19 | | - - **Key**: `portal_hmac_secret` |
20 | | - - **Value**: Copy the HMAC secret from Azure Key Vault (`external-widget-hmac-secret` in the shared Key Vault) |
21 | | - |
22 | | -> **Important**: The secret value must match exactly between the portal and forum. After the initial Terraform apply, retrieve the value from Azure Key Vault and paste it into the forum setting. |
23 | | -
|
24 | | -## Step 2: Add the Widget to the Forum Template |
25 | | - |
26 | | -Edit the Invision Community theme template where you want the widget to appear (e.g., sidebar, footer, or a custom page block). |
27 | | - |
28 | | -### Option A: Global Template (sidebar on all pages) |
29 | | - |
30 | | -Edit the **globalTemplate** or **sidebar** template in your theme: |
31 | | - |
32 | | -```html |
33 | | -{{if member.member_id}} |
34 | | - <?php |
35 | | - $secret = \IPS\Settings::i()->portal_hmac_secret; |
36 | | - $memberId = \IPS\Member::loggedIn()->member_id; |
37 | | - $timestamp = time(); |
38 | | - $hmac = hash_hmac('sha256', "{$memberId}:{$timestamp}", $secret); |
39 | | - $token = base64_encode("{$memberId}:{$timestamp}:{$hmac}"); |
40 | | - ?> |
41 | | - <div id="portal-notifications-widget" |
42 | | - data-token="<?php echo $token; ?>" |
43 | | - data-portal-url="https://portal.xtremeidiots.com"> |
44 | | - </div> |
45 | | -{{else}} |
46 | | - <div id="portal-notifications-widget" |
47 | | - data-token="" |
48 | | - data-portal-url="https://portal.xtremeidiots.com"> |
49 | | - </div> |
50 | | -{{endif}} |
51 | | -<script src="https://portal.xtremeidiots.com/js/forum-widget.js" defer></script> |
52 | | -``` |
| 9 | +### Step 1: Install the Settings Plugin |
| 10 | + |
| 11 | +1. Upload `docs/invision-plugin/plugin.xml` via **ACP → System → Plugins → Install New Plugin** |
| 12 | +2. Click the cog icon next to the plugin to open settings |
| 13 | +3. Set **HMAC Shared Secret** — paste from Azure Key Vault (`external-widget-hmac-secret`) |
| 14 | +4. Set **Portal Base URL** — `https://portal.xtremeidiots.com` (default) |
| 15 | +5. Set **Enable Widget** — Yes |
| 16 | + |
| 17 | +This plugin only registers settings — it does not inject any code or hooks. |
53 | 18 |
|
54 | | -### Option B: Using an Invision Community Plugin Hook |
| 19 | +### Step 2: Create a Custom PHP Block |
55 | 20 |
|
56 | | -If you prefer not to mix PHP into templates, create a simple plugin: |
| 21 | +1. Go to **ACP → Pages → Blocks → Create New Block → Custom → PHP** |
| 22 | +2. Give it a name (e.g., "Portal Notifications") |
| 23 | +3. Paste the following as the block content: |
57 | 24 |
|
58 | | -**File: `hooks/portalWidget.php`** |
59 | 25 | ```php |
60 | | -class portalWidget |
61 | | -{ |
62 | | - public function globalTemplate($html) |
63 | | - { |
64 | | - $member = \IPS\Member::loggedIn(); |
65 | | - $token = ''; |
66 | | - |
67 | | - if ($member->member_id) { |
68 | | - $secret = \IPS\Settings::i()->portal_hmac_secret; |
69 | | - $memberId = $member->member_id; |
70 | | - $timestamp = time(); |
71 | | - $hmac = hash_hmac('sha256', "{$memberId}:{$timestamp}", $secret); |
72 | | - $token = base64_encode("{$memberId}:{$timestamp}:{$hmac}"); |
73 | | - } |
74 | | - |
75 | | - $widget = <<<HTML |
76 | | -<div id="portal-notifications-widget" |
77 | | - data-token="{$token}" |
78 | | - data-portal-url="https://portal.xtremeidiots.com"> |
79 | | -</div> |
80 | | -<script src="https://portal.xtremeidiots.com/js/forum-widget.js" defer></script> |
81 | | -HTML; |
82 | | - |
83 | | - // Insert before </body> |
84 | | - return str_replace('</body>', $widget . '</body>', $html); |
| 26 | +$portalToken = ''; |
| 27 | +$portalUrl = rtrim( \IPS\Settings::i()->portal_base_url ?: 'https://portal.xtremeidiots.com', '/' ); |
| 28 | +$member = \IPS\Member::loggedIn(); |
| 29 | +if ( $member->member_id ) { |
| 30 | + $secret = \IPS\Settings::i()->portal_hmac_secret; |
| 31 | + if ( !empty( $secret ) ) { |
| 32 | + $memberId = (string) $member->member_id; |
| 33 | + $timestamp = (string) time(); |
| 34 | + $hmac = hash_hmac( 'sha256', "{$memberId}:{$timestamp}", $secret ); |
| 35 | + $portalToken = base64_encode( "{$memberId}:{$timestamp}:{$hmac}" ); |
85 | 36 | } |
86 | 37 | } |
| 38 | +echo '<div id="portal-notifications-widget" data-token="' . htmlspecialchars( $portalToken, ENT_QUOTES, 'UTF-8' ) . '" data-portal-url="' . htmlspecialchars( $portalUrl, ENT_QUOTES, 'UTF-8' ) . '"></div>'; |
| 39 | +echo '<script src="' . htmlspecialchars( $portalUrl, ENT_QUOTES, 'UTF-8' ) . '/js/forum-widget.js" defer></script>'; |
87 | 40 | ``` |
88 | 41 |
|
89 | | -### Option C: Custom HTML Block (simplest) |
| 42 | +4. Save the block |
| 43 | +5. Place the block via the front-end widget manager — drag it into the sidebar or wherever you want it |
90 | 44 |
|
91 | | -If you just want a quick test, use the **Pages** app or a **Custom Block**: |
| 45 | +## Prerequisites |
92 | 46 |
|
93 | | -1. Go to **ACP → Pages → Blocks → Create New Block** |
94 | | -2. Choose **Custom HTML** |
95 | | -3. Paste: |
96 | | -```html |
97 | | -<?php |
98 | | -$token = ''; |
99 | | -$member = \IPS\Member::loggedIn(); |
100 | | -if ($member->member_id) { |
101 | | - $secret = \IPS\Settings::i()->portal_hmac_secret; |
102 | | - $memberId = $member->member_id; |
103 | | - $timestamp = time(); |
104 | | - $hmac = hash_hmac('sha256', "{$memberId}:{$timestamp}", $secret); |
105 | | - $token = base64_encode("{$memberId}:{$timestamp}:{$hmac}"); |
106 | | -} |
107 | | -?> |
108 | | -<div id="portal-notifications-widget" |
109 | | - data-token="<?php echo $token; ?>" |
110 | | - data-portal-url="https://portal.xtremeidiots.com"> |
111 | | -</div> |
112 | | -<script src="https://portal.xtremeidiots.com/js/forum-widget.js" defer></script> |
113 | | -``` |
114 | | -4. Place the block in your desired sidebar/widget area |
| 47 | +- The HMAC shared secret must be configured in both: |
| 48 | + - **Portal**: via Azure App Configuration key `XtremeIdiots:ExternalWidget:HmacSecret` (auto-provisioned by portal-environments Terraform) |
| 49 | + - **Forum**: via the plugin settings page (Step 1 above) |
| 50 | +- The portal must be deployed with the external notifications API (`/api/external/notifications`) |
115 | 51 |
|
116 | 52 | ## How It Works |
117 | 53 |
|
118 | | -1. **Token generation** (forum-side): When a logged-in forum user loads a page, the PHP template generates an HMAC-SHA256 signed token containing their forum member ID and a Unix timestamp, signed with the shared secret. |
| 54 | +1. **Token generation** (forum-side): The plugin widget generates an HMAC-SHA256 signed token containing the logged-in forum member's ID and a Unix timestamp, signed with the shared secret. |
119 | 55 |
|
120 | 56 | 2. **Token format**: `Base64({forumMemberId}:{timestampUnix}:{hmacHex})` |
121 | 57 |
|
122 | | -3. **Widget load**: The JavaScript widget (`forum-widget.js`) reads the token from the `data-token` attribute and calls the portal API. |
| 58 | +3. **Widget load**: The JavaScript widget (`forum-widget.js`, served from the portal) reads the token from the `data-token` attribute and calls the portal API. |
123 | 59 |
|
124 | 60 | 4. **API response**: |
125 | 61 | - **No token / invalid token**: Returns a public feed (recent admin actions) |
@@ -157,7 +93,13 @@ To customise, override the styles in your forum theme CSS: |
157 | 93 |
|
158 | 94 | | Issue | Cause | Fix | |
159 | 95 | |-------|-------|-----| |
160 | | -| Widget shows "Portal URL not configured" | Missing `data-portal-url` attribute | Ensure the attribute is set in the template | |
| 96 | +| Widget shows "Portal URL not configured" | Missing portal base URL in plugin settings | Check plugin settings in ACP | |
161 | 97 | | Widget shows "Unable to load notifications" | CORS or network error | Check browser console; verify portal CORS allows the forum domain | |
162 | | -| All users see public feed only | HMAC secret mismatch | Ensure the forum `portal_hmac_secret` matches the Azure Key Vault value | |
| 98 | +| All users see public feed only | HMAC secret mismatch | Ensure the plugin's HMAC secret matches the Azure Key Vault value | |
163 | 99 | | Token always expired | Server clock drift | Ensure both servers have NTP synced clocks (±1 minute tolerance) | |
| 100 | + |
| 101 | +## Updating |
| 102 | + |
| 103 | +To update the plugin, upload the new `plugin.xml` via ACP → System → Plugins. Settings are preserved across updates. |
| 104 | + |
| 105 | +To rotate the HMAC secret: update both the Azure Key Vault value and the plugin setting in ACP. Changes take effect immediately. |
0 commit comments