pptb-standard-sample-tool 1.5.1 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,28 +7,29 @@ A complete example tool for Power Platform Tool Box built with HTML, CSS, and Ty
7
7
  This sample demonstrates:
8
8
 
9
9
  - ✅ **ToolBox API Integration**
10
- - Connection management and status display
11
- - Notifications (success, info, warning, error)
12
- - Clipboard operations
13
- - File save dialogs
14
- - Tool settings storage (save/load/clear)
15
- - Theme detection
16
- - Terminal creation and command execution
17
- - Event subscription and handling
10
+ - Connection management and status display
11
+ - Notifications (success, info, warning, error)
12
+ - Clipboard operations
13
+ - File save dialogs
14
+ - Tool settings storage (save/load/clear)
15
+ - Theme detection
16
+ - Terminal creation and command execution
17
+ - Event subscription and handling
18
+ - Prevent Close / Release Prevent Close (guard against accidental tool/app closure)
18
19
 
19
20
  - ✅ **Dataverse API Usage**
20
- - FetchXML queries
21
- - Multi-connection queries (primary and secondary)
22
- - CRUD operations (Create, Read, Update, Delete)
23
- - Entity metadata retrieval
24
- - Error handling
21
+ - FetchXML queries
22
+ - Multi-connection queries (primary and secondary)
23
+ - CRUD operations (Create, Read, Update, Delete)
24
+ - Entity metadata retrieval
25
+ - Error handling
25
26
 
26
27
  - ✅ **Best Practices**
27
- - TypeScript with full type safety
28
- - Event-driven architecture
29
- - Proper error handling
30
- - Clean, modern UI design
31
- - Responsive layout
28
+ - TypeScript with full type safety
29
+ - Event-driven architecture
30
+ - Proper error handling
31
+ - Clean, modern UI design
32
+ - Responsive layout
32
33
 
33
34
  ## Installation
34
35
 
@@ -79,6 +80,7 @@ html-sample/
79
80
  ### Features Overview
80
81
 
81
82
  #### Connection Status
83
+
82
84
  - Shows current Dataverse connection details
83
85
  - Displays environment type (Production, Sandbox, Dev)
84
86
  - Updates automatically when connection changes
@@ -86,15 +88,24 @@ html-sample/
86
88
  #### ToolBox API Examples
87
89
 
88
90
  **Notifications:**
91
+
89
92
  - Test different notification types
90
93
  - Success, info, warning, and error messages
91
94
 
92
95
  **Utilities:**
96
+
93
97
  - Copy data to clipboard
94
98
  - Get current theme (light/dark)
95
99
  - Save data to file with native dialog
96
100
 
101
+ **Prevent Close:**
102
+
103
+ - Call `preventClose()` to mark this tool instance as blocking closure (e.g. to guard unsaved changes)
104
+ - While enabled, closing the tool's tab or quitting PPTB shows a warning dialog with an "Ignore & Close" override
105
+ - Call `releasePreventClose()` to clear the guard once changes are saved
106
+
97
107
  **Terminal:**
108
+
98
109
  - Create isolated terminal instances
99
110
  - Execute shell commands
100
111
  - Run a safe terminal security probe (non-destructive)
@@ -105,22 +116,26 @@ html-sample/
105
116
  #### Dataverse API Examples
106
117
 
107
118
  **Query Records:**
119
+
108
120
  - FetchXML query to retrieve top 10 accounts
109
121
  - Display results with formatting
110
122
  - If a FetchXML is saved in Tool Settings, the Query button will use that instead of the default
111
- - This sample supports multi-connection: try the "Secondary" buttons to run the same queries against the secondary connection
123
+ - This sample supports multi-connection: try the "Secondary" buttons to run the same queries against the secondary connection
112
124
 
113
125
  **CRUD Operations:**
126
+
114
127
  - Create new account records
115
128
  - Update existing records
116
129
  - Delete records
117
130
  - Full error handling
118
131
 
119
132
  **Metadata:**
133
+
120
134
  - Retrieve entity metadata
121
135
  - Display entity information and attributes
122
136
 
123
137
  #### Event Log
138
+
124
139
  - Real-time event logging
125
140
  - Color-coded by severity
126
141
  - Timestamp for each entry
@@ -160,29 +175,35 @@ const dataverse: typeof window.dataverseAPI = window.dataverseAPI;
160
175
  Use the built-in **Run Security Probe** button in the Terminal section to validate terminal exposure with safe commands only.
161
176
 
162
177
  What it checks:
178
+
163
179
  - Terminal can be created and receives command output
164
180
  - Basic command execution works
165
181
  - Command chaining is possible (risk signal if unrestricted)
166
182
 
167
183
  What it does not do:
184
+
168
185
  - No destructive commands
169
186
  - No credential, SSH, or private file access attempts
170
187
  - No process memory dumping attempts
171
188
 
172
189
  If the probe succeeds, treat that as a signal to enforce stricter host-side controls in Power Platform Tool Box:
190
+
173
191
  - Command allow-listing
174
192
  - Path allow-listing for filesystem APIs
175
193
  - Auditing/logging for terminal command execution
176
194
 
177
195
  This sample now includes policy guards in [src/security/policy.ts](src/security/policy.ts):
196
+
178
197
  - `getBlockedCommandReason(...)` / `getBlockedPathReason(...)`: policy decisions
179
198
  - `executeCommandWithPolicyGuard(...)` / `readTextWithPolicyGuard(...)`: central enforcement wrappers
180
199
 
181
200
  Security suites and report formatting live in:
201
+
182
202
  - [src/security/suites.ts](src/security/suites.ts)
183
203
  - [src/security/reporting.ts](src/security/reporting.ts)
184
204
 
185
205
  It also includes **API-specific Security Suite** buttons plus an **All Suites** runner. Each suite emits a JSON report with per-test `severity` and a rolled-up `highestSeverity`:
206
+
186
207
  - **Terminal suite:** command allow-list enforcement, multiline/control-character blocking, overlong command blocking, local-data-to-network exfil pattern blocking, burst handling
187
208
  - **FileSystem suite:** absolute-path enforcement, traversal blocking, sensitive path blocking, guarded read rejection checks, API surface checks
188
209
  - **Events suite:** event API presence and malformed payload resilience checks
@@ -200,14 +221,14 @@ Below demonstrates using `executeParallel` to run multiple Dataverse operations
200
221
  ```typescript
201
222
  // Execute multiple operations in parallel
202
223
  const [account, contact, opportunities] = await toolboxAPI.utils.executeParallel(
203
- dataverseAPI.retrieve('account', accountId, ['name']),
204
- dataverseAPI.retrieve('contact', contactId, ['fullname']),
205
- dataverseAPI.fetchXmlQuery(opportunityFetchXml)
224
+ dataverseAPI.retrieve("account", accountId, ["name"]),
225
+ dataverseAPI.retrieve("contact", contactId, ["fullname"]),
226
+ dataverseAPI.fetchXmlQuery(opportunityFetchXml),
206
227
  );
207
- console.log('All data fetched:', account, contact, opportunities);
228
+ console.log("All data fetched:", account, contact, opportunities);
208
229
 
209
230
  // Show loading screen during operations
210
- await toolboxAPI.utils.showLoading('Processing data...');
231
+ await toolboxAPI.utils.showLoading("Processing data...");
211
232
  try {
212
233
  // Perform operations
213
234
  await processData();
@@ -225,13 +246,13 @@ Use the tool settings API to persist user preferences and configuration for your
225
246
 
226
247
  ```typescript
227
248
  // Save a setting
228
- await toolboxAPI.settings.set('demo.fetchxml', myFetchXmlString);
249
+ await toolboxAPI.settings.set("demo.fetchxml", myFetchXmlString);
229
250
 
230
251
  // Read a setting
231
- const saved = await toolboxAPI.settings.get('demo.fetchxml');
252
+ const saved = await toolboxAPI.settings.get("demo.fetchxml");
232
253
 
233
254
  // Delete a setting
234
- await toolboxAPI.settings.delete('demo.fetchxml');
255
+ await toolboxAPI.settings.delete("demo.fetchxml");
235
256
  ```
236
257
 
237
258
  In this sample, the “Tool Settings” section lets you save a FetchXML snippet. The “Query Top 10 Accounts” button will use the saved FetchXML if present, otherwise it falls back to the default.
@@ -241,10 +262,10 @@ In this sample, the “Tool Settings” section lets you save a FetchXML snippet
241
262
  ```typescript
242
263
  // Show notification
243
264
  await toolbox.utils.showNotification({
244
- title: 'Success',
245
- body: 'Operation completed',
246
- type: 'success',
247
- duration: 3000
265
+ title: "Success",
266
+ body: "Operation completed",
267
+ type: "success",
268
+ duration: 3000,
248
269
  });
249
270
 
250
271
  // Get active connection
@@ -252,12 +273,12 @@ const connection = await toolbox.connections.getActiveConnection();
252
273
 
253
274
  // Create terminal
254
275
  const terminal = await toolbox.terminal.create({
255
- name: 'My Terminal'
276
+ name: "My Terminal",
256
277
  });
257
278
 
258
279
  // Subscribe to events
259
280
  toolbox.events.on((event, payload) => {
260
- console.log('Event:', payload.event, payload.data);
281
+ console.log("Event:", payload.event, payload.data);
261
282
  });
262
283
  ```
263
284
 
@@ -274,34 +295,37 @@ const result = await dataverse.fetchXmlQuery(`
274
295
  `);
275
296
 
276
297
  // Query with FetchXML targeting the secondary connection
277
- const secondaryResult = await dataverse.fetchXmlQuery(`
298
+ const secondaryResult = await dataverse.fetchXmlQuery(
299
+ `
278
300
  <fetch top="5">
279
301
  <entity name="account">
280
302
  <attribute name="name" />
281
303
  <order attribute="name" />
282
304
  </entity>
283
305
  </fetch>
284
- `, 'secondary');
306
+ `,
307
+ "secondary",
308
+ );
285
309
 
286
310
  // Create record
287
- const account = await dataverse.create('account', {
288
- name: 'Contoso Ltd',
289
- emailaddress1: 'info@contoso.com'
311
+ const account = await dataverse.create("account", {
312
+ name: "Contoso Ltd",
313
+ emailaddress1: "info@contoso.com",
290
314
  });
291
315
 
292
316
  // Update record
293
- await dataverse.update('account', accountId, {
294
- telephone1: '555-0100'
317
+ await dataverse.update("account", accountId, {
318
+ telephone1: "555-0100",
295
319
  });
296
320
 
297
321
  // Delete record
298
- await dataverse.delete('account', accountId);
322
+ await dataverse.delete("account", accountId);
299
323
 
300
324
  // Get metadata
301
- const metadata = await dataverse.getEntityMetadata('account');
325
+ const metadata = await dataverse.getEntityMetadata("account");
302
326
 
303
327
  // Get metadata on secondary
304
- const metadataSecondary = await dataverse.getEntityMetadata('account', true, ['LogicalName'], 'secondary');
328
+ const metadataSecondary = await dataverse.getEntityMetadata("account", true, ["LogicalName"], "secondary");
305
329
  ```
306
330
 
307
331
  ## Troubleshooting
@@ -309,6 +333,7 @@ const metadataSecondary = await dataverse.getEntityMetadata('account', true, ['L
309
333
  ### Build Errors
310
334
 
311
335
  If you encounter TypeScript errors:
336
+
312
337
  1. Ensure `@pptb/types` is installed: `npm install`
313
338
  2. Check TypeScript version: `tsc --version` (should be 5.x)
314
339
  3. Clean and rebuild: `rm -rf dist && npm run build`
@@ -316,6 +341,7 @@ If you encounter TypeScript errors:
316
341
  ### API Not Available
317
342
 
318
343
  If `toolboxAPI` or `dataverseAPI` is undefined:
344
+
319
345
  - The tool must be loaded within Power Platform Tool Box
320
346
  - These APIs are injected by the toolboxAPIBridge
321
347
  - They are not available in a standalone browser
@@ -323,6 +349,7 @@ If `toolboxAPI` or `dataverseAPI` is undefined:
323
349
  ### Connection Issues
324
350
 
325
351
  If connection is null:
352
+
326
353
  - Open Power Platform Tool Box
327
354
  - Create a connection to a Dataverse environment
328
355
  - The tool will automatically detect the connection
package/dist/app.js CHANGED
@@ -22,6 +22,7 @@ let secondaryConnection = null;
22
22
  let currentTerminal = null;
23
23
  let createdId = null;
24
24
  let currentLaunchContext = null;
25
+ let preventCloseActive = false;
25
26
  let securitySuites = null;
26
27
  let terminalFeature = null;
27
28
  let fileSystemFeature = null;
@@ -297,6 +298,9 @@ function setupEventHandlers() {
297
298
  // Advanced utilities demos
298
299
  document.getElementById("parallel-demo-btn")?.addEventListener("click", demoExecuteParallel);
299
300
  document.getElementById("loading-demo-btn")?.addEventListener("click", demoLoading);
301
+ // Prevent Close buttons
302
+ document.getElementById("enable-prevent-close-btn")?.addEventListener("click", enablePreventClose);
303
+ document.getElementById("disable-prevent-close-btn")?.addEventListener("click", disablePreventClose);
300
304
  // Settings buttons
301
305
  document.getElementById("load-setting-btn")?.addEventListener("click", loadToolSetting);
302
306
  document.getElementById("save-setting-btn")?.addEventListener("click", saveToolSetting);
@@ -364,6 +368,52 @@ async function showCurrentTheme() {
364
368
  log(`Error getting theme: ${error.message}`, "error");
365
369
  }
366
370
  }
371
+ /**
372
+ * Update the Prevent Close status UI to reflect the current state
373
+ */
374
+ function updatePreventCloseStatusUI() {
375
+ const status = document.getElementById("prevent-close-status");
376
+ const enableBtn = document.getElementById("enable-prevent-close-btn");
377
+ const disableBtn = document.getElementById("disable-prevent-close-btn");
378
+ if (status) {
379
+ status.className = preventCloseActive ? "info-box warning" : "info-box";
380
+ status.textContent = preventCloseActive ? "Prevent close is ENABLED. Closing this tab or quitting PPTB will show a warning dialog." : "Prevent close is currently disabled.";
381
+ }
382
+ if (enableBtn)
383
+ enableBtn.disabled = preventCloseActive;
384
+ if (disableBtn)
385
+ disableBtn.disabled = !preventCloseActive;
386
+ }
387
+ /**
388
+ * Enable prevent-close for this tool instance (e.g. to guard unsaved changes)
389
+ */
390
+ async function enablePreventClose() {
391
+ try {
392
+ await toolbox.preventClose();
393
+ preventCloseActive = true;
394
+ updatePreventCloseStatusUI();
395
+ log("Prevent close enabled", "warning");
396
+ await showNotification("Prevent Close Enabled", "Closing this tool now requires confirmation", "warning");
397
+ }
398
+ catch (error) {
399
+ log(`Error enabling prevent close: ${error.message}`, "error");
400
+ }
401
+ }
402
+ /**
403
+ * Release the prevent-close guard for this tool instance
404
+ */
405
+ async function disablePreventClose() {
406
+ try {
407
+ await toolbox.releasePreventClose();
408
+ preventCloseActive = false;
409
+ updatePreventCloseStatusUI();
410
+ log("Prevent close disabled", "info");
411
+ await showNotification("Prevent Close Disabled", "This tool can now be closed without confirmation", "info");
412
+ }
413
+ catch (error) {
414
+ log(`Error disabling prevent close: ${error.message}`, "error");
415
+ }
416
+ }
367
417
  function requireTerminalFeature() {
368
418
  if (!terminalFeature) {
369
419
  throw new Error("Terminal feature not initialized");
package/dist/index.html CHANGED
@@ -75,6 +75,19 @@
75
75
  <div id="parallel-output" class="output"></div>
76
76
  </div>
77
77
 
78
+ <div class="example-group">
79
+ <h3>Prevent Close</h3>
80
+ <p style="margin: 4px 0 8px; font-size: 12px; opacity: 0.8">
81
+ Demonstrates <code>preventClose</code> / <code>releasePreventClose</code>. While enabled, closing this tool's tab or quitting PPTB shows a warning dialog with an
82
+ "Ignore & Close" override.
83
+ </p>
84
+ <div class="button-group">
85
+ <button id="enable-prevent-close-btn" class="btn btn-warning">Enable Prevent Close</button>
86
+ <button id="disable-prevent-close-btn" class="btn" disabled>Disable Prevent Close</button>
87
+ </div>
88
+ <div id="prevent-close-status" class="info-box">Prevent close is currently disabled.</div>
89
+ </div>
90
+
78
91
  <div class="example-group">
79
92
  <h3>Terminal</h3>
80
93
  <div class="button-group">
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "pptb-standard-sample-tool",
3
- "version": "1.4.4",
3
+ "version": "1.5.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pptb-standard-sample-tool",
9
- "version": "1.4.4",
9
+ "version": "1.5.1",
10
10
  "license": "GPL-3.0",
11
11
  "devDependencies": {
12
- "@pptb/types": "^1.2.6-beta.1",
12
+ "@pptb/types": "^1.2.6-beta.2",
13
13
  "shx": "^0.4.0",
14
14
  "typescript": "^5.0.0"
15
15
  },
@@ -69,22 +69,22 @@
69
69
  }
70
70
  },
71
71
  "node_modules/@pptb/types": {
72
- "version": "1.2.6-beta.1",
73
- "resolved": "https://registry.npmjs.org/@pptb/types/-/types-1.2.6-beta.1.tgz",
74
- "integrity": "sha512-ilBTcr6u2lVlab2eSLVOQ6eOybLZYhEq8bBq82MTCDjVcJjxKDB0nRyH9aV8+iqTHORWgUpiBOBhl4KWrv8Czw==",
72
+ "version": "1.2.6-beta.2",
73
+ "resolved": "https://registry.npmjs.org/@pptb/types/-/types-1.2.6-beta.2.tgz",
74
+ "integrity": "sha512-k6Ov2Jfl3c2Et4h86fiKPZK2E0i/hIMGDNnXsXZgREAwU4tMtBmcqrQNr6YfFJOIRzM2fSbQDqW9bGecup5i2Q==",
75
75
  "dev": true,
76
76
  "license": "GPL-3.0",
77
77
  "dependencies": {
78
- "@pptb/validate": "1.0.2-beta.0"
78
+ "@pptb/validate": "1.0.2"
79
79
  },
80
80
  "bin": {
81
81
  "pptb-validate": "bin/pptb-validate.js"
82
82
  }
83
83
  },
84
84
  "node_modules/@pptb/validate": {
85
- "version": "1.0.2-beta.0",
86
- "resolved": "https://registry.npmjs.org/@pptb/validate/-/validate-1.0.2-beta.0.tgz",
87
- "integrity": "sha512-2fVvDgk4mizC8IJHrqyGRO3cSfe+NWfKHs0yQm7VpWf+tTXdqASliLhLKo1ZjnoDUFEdwMSQuFpMlQTl61hf8A==",
85
+ "version": "1.0.2",
86
+ "resolved": "https://registry.npmjs.org/@pptb/validate/-/validate-1.0.2.tgz",
87
+ "integrity": "sha512-D6bfEa1a7p9ju7BUY2l/jJ8tK7s5SEtpvNriBRfasazP91mJ6eL5mLqOGwwjQ9aPyRJoeFyVmdF4gRyFMCcG7Q==",
88
88
  "dev": true,
89
89
  "license": "GPL-3.0",
90
90
  "dependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pptb-standard-sample-tool",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
4
4
  "displayName": "HTML Sample Tool",
5
5
  "description": "A sample Power Platform ToolBox tool built with HTML, CSS, and TypeScript",
6
6
  "main": "index.html",
@@ -79,7 +79,7 @@
79
79
  "validate": "pptb-validate"
80
80
  },
81
81
  "devDependencies": {
82
- "@pptb/types": "^1.2.6-beta.1",
82
+ "@pptb/types": "^1.2.6-beta.2",
83
83
  "shx": "^0.4.0",
84
84
  "typescript": "^5.0.0"
85
85
  },