@apvee/spfx-react-toolkit 1.3.0 → 2.0.0

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.
Files changed (124) hide show
  1. package/lib/core/atoms.internal.js +1 -1
  2. package/lib/core/atoms.internal.js.map +1 -1
  3. package/lib/core/context.internal.js +2 -2
  4. package/lib/core/context.internal.js.map +1 -1
  5. package/lib/core/provider-base.internal.js +29 -29
  6. package/lib/core/provider-base.internal.js.map +1 -1
  7. package/lib/extensions/spFxReactToolkitTest/SpFxReactToolkitTestApplicationCustomizer.js +8 -29
  8. package/lib/extensions/spFxReactToolkitTest/SpFxReactToolkitTestApplicationCustomizer.js.map +1 -1
  9. package/lib/hooks/index.d.ts +1 -0
  10. package/lib/hooks/index.d.ts.map +1 -1
  11. package/lib/hooks/index.js +1 -0
  12. package/lib/hooks/index.js.map +1 -1
  13. package/lib/hooks/useAppCatalogUrl.internal.d.ts +26 -0
  14. package/lib/hooks/useAppCatalogUrl.internal.d.ts.map +1 -0
  15. package/lib/hooks/useAppCatalogUrl.internal.js +72 -0
  16. package/lib/hooks/useAppCatalogUrl.internal.js.map +1 -0
  17. package/lib/hooks/useAsyncInvoke.internal.js +27 -75
  18. package/lib/hooks/useAsyncInvoke.internal.js.map +1 -1
  19. package/lib/hooks/useSPFxAadHttpClient.js +27 -27
  20. package/lib/hooks/useSPFxAadHttpClient.js.map +1 -1
  21. package/lib/hooks/useSPFxContainerInfo.js +5 -5
  22. package/lib/hooks/useSPFxContainerInfo.js.map +1 -1
  23. package/lib/hooks/useSPFxContainerSize.js +9 -10
  24. package/lib/hooks/useSPFxContainerSize.js.map +1 -1
  25. package/lib/hooks/useSPFxCorrelationInfo.js +6 -7
  26. package/lib/hooks/useSPFxCorrelationInfo.js.map +1 -1
  27. package/lib/hooks/useSPFxCrossSitePermissions.js +48 -58
  28. package/lib/hooks/useSPFxCrossSitePermissions.js.map +1 -1
  29. package/lib/hooks/useSPFxDisplayMode.js +8 -8
  30. package/lib/hooks/useSPFxDisplayMode.js.map +1 -1
  31. package/lib/hooks/useSPFxEnvironmentInfo.js +17 -18
  32. package/lib/hooks/useSPFxEnvironmentInfo.js.map +1 -1
  33. package/lib/hooks/useSPFxFluent9ThemeInfo.js +4 -4
  34. package/lib/hooks/useSPFxFluent9ThemeInfo.js.map +1 -1
  35. package/lib/hooks/useSPFxHttpClient.js +10 -10
  36. package/lib/hooks/useSPFxHttpClient.js.map +1 -1
  37. package/lib/hooks/useSPFxHubSiteInfo.js +21 -24
  38. package/lib/hooks/useSPFxHubSiteInfo.js.map +1 -1
  39. package/lib/hooks/useSPFxInstanceInfo.js +2 -2
  40. package/lib/hooks/useSPFxInstanceInfo.js.map +1 -1
  41. package/lib/hooks/useSPFxListInfo.js +8 -9
  42. package/lib/hooks/useSPFxListInfo.js.map +1 -1
  43. package/lib/hooks/useSPFxLocaleInfo.js +10 -10
  44. package/lib/hooks/useSPFxLocaleInfo.js.map +1 -1
  45. package/lib/hooks/useSPFxLogger.js +26 -26
  46. package/lib/hooks/useSPFxLogger.js.map +1 -1
  47. package/lib/hooks/useSPFxMSGraphClient.js +25 -25
  48. package/lib/hooks/useSPFxMSGraphClient.js.map +1 -1
  49. package/lib/hooks/useSPFxOneDriveAppData.js +148 -209
  50. package/lib/hooks/useSPFxOneDriveAppData.js.map +1 -1
  51. package/lib/hooks/useSPFxPageContext.js +2 -2
  52. package/lib/hooks/useSPFxPageContext.js.map +1 -1
  53. package/lib/hooks/useSPFxPageType.js +19 -20
  54. package/lib/hooks/useSPFxPageType.js.map +1 -1
  55. package/lib/hooks/useSPFxPerformance.js +33 -87
  56. package/lib/hooks/useSPFxPerformance.js.map +1 -1
  57. package/lib/hooks/useSPFxPermissions.js +14 -15
  58. package/lib/hooks/useSPFxPermissions.js.map +1 -1
  59. package/lib/hooks/useSPFxPnP.js +62 -119
  60. package/lib/hooks/useSPFxPnP.js.map +1 -1
  61. package/lib/hooks/useSPFxPnPContext.js +22 -25
  62. package/lib/hooks/useSPFxPnPContext.js.map +1 -1
  63. package/lib/hooks/useSPFxPnPList.js +307 -451
  64. package/lib/hooks/useSPFxPnPList.js.map +1 -1
  65. package/lib/hooks/useSPFxPnPSearch.js +262 -353
  66. package/lib/hooks/useSPFxPnPSearch.js.map +1 -1
  67. package/lib/hooks/useSPFxProperties.js +12 -20
  68. package/lib/hooks/useSPFxProperties.js.map +1 -1
  69. package/lib/hooks/useSPFxSPHttpClient.js +19 -19
  70. package/lib/hooks/useSPFxSPHttpClient.js.map +1 -1
  71. package/lib/hooks/useSPFxServiceScope.js +6 -6
  72. package/lib/hooks/useSPFxServiceScope.js.map +1 -1
  73. package/lib/hooks/useSPFxSiteInfo.js +7 -8
  74. package/lib/hooks/useSPFxSiteInfo.js.map +1 -1
  75. package/lib/hooks/useSPFxStorage.js +22 -22
  76. package/lib/hooks/useSPFxStorage.js.map +1 -1
  77. package/lib/hooks/useSPFxTeams.js +37 -92
  78. package/lib/hooks/useSPFxTeams.js.map +1 -1
  79. package/lib/hooks/useSPFxTenantKeyValueStore.d.ts +252 -0
  80. package/lib/hooks/useSPFxTenantKeyValueStore.d.ts.map +1 -0
  81. package/lib/hooks/useSPFxTenantKeyValueStore.js +572 -0
  82. package/lib/hooks/useSPFxTenantKeyValueStore.js.map +1 -0
  83. package/lib/hooks/useSPFxTenantProperty.d.ts +23 -244
  84. package/lib/hooks/useSPFxTenantProperty.d.ts.map +1 -1
  85. package/lib/hooks/useSPFxTenantProperty.js +85 -559
  86. package/lib/hooks/useSPFxTenantProperty.js.map +1 -1
  87. package/lib/hooks/useSPFxUserInfo.js +3 -4
  88. package/lib/hooks/useSPFxUserInfo.js.map +1 -1
  89. package/lib/hooks/useSPFxUserPhoto.js +76 -123
  90. package/lib/hooks/useSPFxUserPhoto.js.map +1 -1
  91. package/lib/utils/resize-observer.internal.js +6 -7
  92. package/lib/utils/resize-observer.internal.js.map +1 -1
  93. package/lib/utils/theme-subscription.internal.js +8 -8
  94. package/lib/utils/theme-subscription.internal.js.map +1 -1
  95. package/lib/utils/type-guards.internal.js +6 -6
  96. package/lib/utils/type-guards.internal.js.map +1 -1
  97. package/lib/webparts/spFxReactToolkitTest/SpFxReactToolkitTestWebPart.js +12 -37
  98. package/lib/webparts/spFxReactToolkitTest/SpFxReactToolkitTestWebPart.js.map +1 -1
  99. package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.d.ts.map +1 -1
  100. package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.js +277 -336
  101. package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.js.map +1 -1
  102. package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.module.scss.js +1 -1
  103. package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.module.scss.js.map +1 -1
  104. package/lib/webparts/spFxReactToolkitTest/components/demos/HttpClientDemo.js +26 -86
  105. package/lib/webparts/spFxReactToolkitTest/components/demos/HttpClientDemo.js.map +1 -1
  106. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPContextDemo.js +53 -113
  107. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPContextDemo.js.map +1 -1
  108. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPListDemo.js +49 -121
  109. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPListDemo.js.map +1 -1
  110. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPOperationsDemo.js +44 -103
  111. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPOperationsDemo.js.map +1 -1
  112. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchAdvancedDemo.js +15 -15
  113. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchAdvancedDemo.js.map +1 -1
  114. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchBasicDemo.js +18 -66
  115. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchBasicDemo.js.map +1 -1
  116. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchRefinersDemo.js +9 -9
  117. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchRefinersDemo.js.map +1 -1
  118. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchSuggestionsDemo.js +37 -86
  119. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchSuggestionsDemo.js.map +1 -1
  120. package/lib/webparts/spFxReactToolkitTest/components/shared/InfoRow.js +6 -9
  121. package/lib/webparts/spFxReactToolkitTest/components/shared/InfoRow.js.map +1 -1
  122. package/lib/webparts/spFxReactToolkitTest/components/shared/StatusBadge.js +3 -6
  123. package/lib/webparts/spFxReactToolkitTest/components/shared/StatusBadge.js.map +1 -1
  124. package/package.json +1 -1
@@ -1,151 +1,51 @@
1
1
  // useSPFxTenantProperty.ts
2
- // Hook to manage tenant-wide properties using SharePoint StorageEntity API
3
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
4
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
5
- return new (P || (P = Promise))(function (resolve, reject) {
6
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
7
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
8
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
9
- step((generator = generator.apply(thisArg, _arguments || [])).next());
10
- });
11
- };
12
- var __generator = (this && this.__generator) || function (thisArg, body) {
13
- var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
14
- return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
15
- function verb(n) { return function (v) { return step([n, v]); }; }
16
- function step(op) {
17
- if (f) throw new TypeError("Generator is already executing.");
18
- while (g && (g = 0, op[0] && (_ = 0)), _) try {
19
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
20
- if (y = 0, t) op = [op[0] & 2, t.value];
21
- switch (op[0]) {
22
- case 0: case 1: t = op; break;
23
- case 4: _.label++; return { value: op[1], done: false };
24
- case 5: _.label++; y = op[1]; op = [0]; continue;
25
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
26
- default:
27
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
28
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
29
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
30
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
31
- if (t[2]) _.ops.pop();
32
- _.trys.pop(); continue;
33
- }
34
- op = body.call(thisArg, _);
35
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
36
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
37
- }
38
- };
39
- import { useState, useCallback, useEffect, useRef } from 'react';
40
- import { useSPFxSPHttpClient } from './useSPFxSPHttpClient';
41
- import { useSPFxPageContext } from './useSPFxPageContext';
2
+ // Hook to read tenant-wide properties using SharePoint StorageEntity API (read-only)
3
+ import { useState, useCallback, useEffect } from 'react';
4
+ import { useAppCatalogUrl } from './useAppCatalogUrl.internal';
42
5
  import { SPHttpClient } from '@microsoft/sp-http';
43
6
  /**
44
- * Hook to manage tenant-wide properties using SharePoint StorageEntity API
7
+ * Hook to read tenant-wide properties using SharePoint StorageEntity API (read-only)
8
+ *
9
+ * Provides read access to tenant-scoped properties stored in the SharePoint tenant
10
+ * app catalog. Properties are accessible across all sites in the tenant.
45
11
  *
46
- * Provides read/write operations for tenant-scoped properties stored in the
47
- * SharePoint tenant app catalog. Properties are accessible across all sites
48
- * in the tenant and support metadata (description only).
12
+ * **Note:** Write and remove operations have been removed because Microsoft has
13
+ * blocked the SetStorageEntity and RemoveStorageEntity REST API endpoints.
14
+ * Tenant properties can only be managed via PowerShell (Set-PnPStorageEntity,
15
+ * Remove-PnPStorageEntity) or the SharePoint Management Shell.
16
+ * For a read/write key-value store at tenant level, use `useSPFxTenantKeyValueStore`.
49
17
  *
50
18
  * Features:
51
19
  * - Tenant-wide centralized storage (not site-specific)
52
- * - Smart serialization for primitives, Date, and complex objects
53
- * - Permission checking (canWrite flag)
20
+ * - Smart deserialization for primitives and complex objects
54
21
  * - Optional metadata (description only - SharePoint limitation)
55
22
  * - Type-safe with TypeScript generics
56
23
  * - Memory leak safe with mounted state tracking
57
24
  * - Automatic app catalog URL discovery
58
- * - Remove operation for cleanup
59
25
  *
60
26
  * Requirements:
61
27
  * - Tenant app catalog must be provisioned
62
- * - Read: Any authenticated user
63
- * - Write/Remove: Site Collection Administrator role on tenant app catalog site
64
- *
65
- * Permission Notes:
66
- * - Being a Tenant Admin is NOT sufficient for write operations
67
- * - You must be explicitly added as Site Collection Administrator to the tenant app catalog
68
- * - Navigate to the app catalog site → Site Settings → Site Collection Administrators
69
- * - Add your user account if write operations fail with permission errors
70
- *
71
- * Storage Format:
72
- * - Primitives (string, number, boolean, null) → stored as string
73
- * - Date → stored as ISO 8601 string
74
- * - Objects/arrays → stored as JSON string
28
+ * - Any authenticated user can read tenant properties
75
29
  *
76
30
  * @param key - Unique property key (e.g., 'appVersion', 'apiEndpoint', 'featureFlags')
77
31
  * @param autoFetch - Whether to automatically load property on mount. Default: true
78
32
  *
79
- * @returns Object with data, metadata, loading states, error states, and CRUD functions
33
+ * @returns Object with data, metadata, loading state, error state, and load function
80
34
  *
81
35
  * @example Basic usage - string property
82
36
  * ```tsx
83
37
  * function VersionDisplay() {
84
- * const { data, isLoading, error, write, canWrite } =
85
- * useSPFxTenantProperty<string>('appVersion');
38
+ * const { data, isLoading, error } = useSPFxTenantProperty<string>('appVersion');
86
39
  *
87
40
  * if (isLoading) return <Spinner label="Loading version..." />;
88
41
  * if (error) return <MessageBar messageBarType={MessageBarType.error}>
89
42
  * Failed to load: {error.message}
90
43
  * </MessageBar>;
91
44
  *
92
- * const handleUpdate = async () => {
93
- * if (!canWrite) {
94
- * alert('Insufficient permissions');
95
- * return;
96
- * }
97
- *
98
- * try {
99
- * await write('2.0.1', 'Current application version');
100
- * console.log('Version updated!');
101
- * } catch (err) {
102
- * console.error('Update failed:', err);
103
- * }
104
- * };
105
- *
106
- * return (
107
- * <div>
108
- * <Text>Current Version: {data ?? 'Not Set'}</Text>
109
- * {canWrite && <PrimaryButton onClick={handleUpdate}>Update Version</PrimaryButton>}
110
- * </div>
111
- * );
112
- * }
113
- * ```
114
- *
115
- * @example Number property
116
- * ```tsx
117
- * const { data, write } = useSPFxTenantProperty<number>('maxUploadSize');
118
- *
119
- * // Stored as "10485760" (10MB in bytes)
120
- * await write(10485760, 'Maximum file upload size in bytes');
121
- *
122
- * // Read returns: 10485760 (number)
123
- * console.log(typeof data); // "number"
124
- * ```
125
- *
126
- * @example Boolean flag
127
- * ```tsx
128
- * const { data: maintenanceMode, write } = useSPFxTenantProperty<boolean>('maintenanceMode');
129
- *
130
- * // Stored as "true" or "false"
131
- * await write(true, 'Maintenance mode enabled');
132
- *
133
- * if (maintenanceMode) {
134
- * return <MessageBar>System is under maintenance</MessageBar>;
45
+ * return <Text>Current Version: {data ?? 'Not Set'}</Text>;
135
46
  * }
136
47
  * ```
137
48
  *
138
- * @example Date property
139
- * ```tsx
140
- * const { data, write } = useSPFxTenantProperty<string>('lastDeployment');
141
- *
142
- * // Write date as ISO string
143
- * await write(new Date().toISOString(), 'Last deployment timestamp');
144
- *
145
- * // Read and convert back to Date
146
- * const lastDeploy = data ? new Date(data) : undefined;
147
- * ```
148
- *
149
49
  * @example Complex object with JSON
150
50
  * ```tsx
151
51
  * interface FeatureFlags {
@@ -154,74 +54,14 @@ import { SPHttpClient } from '@microsoft/sp-http';
154
54
  * maxUsers: number;
155
55
  * }
156
56
  *
157
- * const { data, write, isLoading } = useSPFxTenantProperty<FeatureFlags>('featureFlags');
57
+ * const { data, isLoading } = useSPFxTenantProperty<FeatureFlags>('featureFlags');
158
58
  *
159
- * // Stored as JSON string
160
- * await write({
161
- * enableChat: true,
162
- * enableAnalytics: false,
163
- * maxUsers: 1000
164
- * }, 'Global feature flags configuration');
165
- *
166
- * // Read returns parsed object
59
+ * // Returns parsed object (stored as JSON string via PowerShell)
167
60
  * if (data?.enableChat) {
168
61
  * return <ChatPanel />;
169
62
  * }
170
63
  * ```
171
64
  *
172
- * @example Permission-aware UI
173
- * ```tsx
174
- * function TenantConfigPanel() {
175
- * const { data, canWrite, write, isWriting, error, writeError } =
176
- * useSPFxTenantProperty<string>('apiEndpoint');
177
- *
178
- * const [editValue, setEditValue] = React.useState(data ?? '');
179
- *
180
- * React.useEffect(() => {
181
- * setEditValue(data ?? '');
182
- * }, [data]);
183
- *
184
- * if (!canWrite) {
185
- * return (
186
- * <MessageBar messageBarType={MessageBarType.info}>
187
- * You don't have permission to edit tenant properties.
188
- * Contact your SharePoint administrator.
189
- * </MessageBar>
190
- * );
191
- * }
192
- *
193
- * const handleSave = async () => {
194
- * try {
195
- * await write(editValue, 'Production API endpoint URL');
196
- * } catch (err) {
197
- * console.error('Save failed:', err);
198
- * }
199
- * };
200
- *
201
- * return (
202
- * <Stack tokens={{ childrenGap: 10 }}>
203
- * <TextField
204
- * label="API Endpoint"
205
- * value={editValue}
206
- * onChange={(_, val) => setEditValue(val ?? '')}
207
- * disabled={isWriting}
208
- * />
209
- * <PrimaryButton
210
- * onClick={handleSave}
211
- * disabled={isWriting || editValue === data}
212
- * >
213
- * {isWriting ? 'Saving...' : 'Save'}
214
- * </PrimaryButton>
215
- * {writeError && (
216
- * <MessageBar messageBarType={MessageBarType.error}>
217
- * {writeError.message}
218
- * </MessageBar>
219
- * )}
220
- * </Stack>
221
- * );
222
- * }
223
- * ```
224
- *
225
65
  * @example With metadata viewing
226
66
  * ```tsx
227
67
  * function PropertyViewer() {
@@ -241,7 +81,7 @@ import { SPHttpClient } from '@microsoft/sp-http';
241
81
  *
242
82
  * @example Lazy loading with manual trigger
243
83
  * ```tsx
244
- * const { data, load, isLoading, write } = useSPFxTenantProperty<Config>(
84
+ * const { data, load, isLoading } = useSPFxTenantProperty<Config>(
245
85
  * 'appConfig',
246
86
  * false // Don't auto-fetch
247
87
  * );
@@ -256,428 +96,114 @@ import { SPHttpClient } from '@microsoft/sp-http';
256
96
  * );
257
97
  * ```
258
98
  *
259
- * @example Property removal
260
- * ```tsx
261
- * function PropertyManager() {
262
- * const { data, remove, canWrite } = useSPFxTenantProperty<string>('deprecatedSetting');
263
- *
264
- * const handleDelete = async () => {
265
- * if (!confirm('Delete this property? This cannot be undone.')) return;
266
- *
267
- * try {
268
- * await remove();
269
- * console.log('Property deleted');
270
- * } catch (err) {
271
- * console.error('Delete failed:', err);
272
- * }
273
- * };
274
- *
275
- * if (!data) return <Text>Property not found</Text>;
276
- *
277
- * return (
278
- * <Stack tokens={{ childrenGap: 10 }}>
279
- * <Text>Value: {data}</Text>
280
- * {canWrite && (
281
- * <DefaultButton onClick={handleDelete} text="Delete Property" />
282
- * )}
283
- * </Stack>
284
- * );
285
- * }
286
- * ```
287
- *
288
99
  * @example Multi-property dashboard
289
100
  * ```tsx
290
101
  * function TenantDashboard() {
291
102
  * const version = useSPFxTenantProperty<string>('appVersion');
292
103
  * const maintenance = useSPFxTenantProperty<boolean>('maintenanceMode');
293
- * const lastUpdate = useSPFxTenantProperty<string>('lastUpdate');
294
104
  * const config = useSPFxTenantProperty<AppConfig>('appConfig');
295
105
  *
296
- * const isLoading = version.isLoading || maintenance.isLoading ||
297
- * lastUpdate.isLoading || config.isLoading;
106
+ * const isLoading = version.isLoading || maintenance.isLoading || config.isLoading;
298
107
  *
299
108
  * if (isLoading) return <Spinner label="Loading dashboard..." />;
300
109
  *
301
110
  * return (
302
111
  * <Stack tokens={{ childrenGap: 20 }}>
303
112
  * <Text variant="xxLarge">Tenant Configuration</Text>
304
- *
305
- * <Stack tokens={{ childrenGap: 10 }}>
306
- * <Label>App Version: {version.data ?? 'Not Set'}</Label>
307
- * <Label>Maintenance Mode: {maintenance.data ? 'ON' : 'OFF'}</Label>
308
- * <Label>Last Update: {lastUpdate.data ? new Date(lastUpdate.data).toLocaleString() : 'Never'}</Label>
309
- * </Stack>
310
- *
311
- * {config.data && (
312
- * <Stack tokens={{ childrenGap: 5 }}>
313
- * <Text variant="large">Configuration</Text>
314
- * <pre>{JSON.stringify(config.data, null, 2)}</pre>
315
- * </Stack>
316
- * )}
113
+ * <Label>App Version: {version.data ?? 'Not Set'}</Label>
114
+ * <Label>Maintenance Mode: {maintenance.data ? 'ON' : 'OFF'}</Label>
115
+ * {config.data && <pre>{JSON.stringify(config.data, null, 2)}</pre>}
317
116
  * </Stack>
318
117
  * );
319
118
  * }
320
119
  * ```
321
120
  */
322
- export function useSPFxTenantProperty(key, autoFetch) {
323
- var _this = this;
324
- if (autoFetch === void 0) { autoFetch = true; }
325
- var spHttpClient = useSPFxSPHttpClient().client;
326
- var pageContext = useSPFxPageContext();
121
+ export function useSPFxTenantProperty(key, autoFetch = true) {
122
+ const { spHttpClient, discoverAppCatalogUrl, isMountedRef } = useAppCatalogUrl();
327
123
  // State management
328
- var _a = useState(undefined), data = _a[0], setData = _a[1];
329
- var _b = useState(undefined), description = _b[0], setDescription = _b[1];
330
- var _c = useState(false), isLoading = _c[0], setIsLoading = _c[1];
331
- var _d = useState(undefined), error = _d[0], setError = _d[1];
332
- var _e = useState(false), isWriting = _e[0], setIsWriting = _e[1];
333
- var _f = useState(undefined), writeError = _f[0], setWriteError = _f[1];
334
- var _g = useState(false), canWrite = _g[0], setCanWrite = _g[1];
335
- var _h = useState(undefined), appCatalogUrl = _h[0], setAppCatalogUrl = _h[1];
336
- // Track component mounted state to prevent memory leaks
337
- var isMounted = useRef(true);
338
- useEffect(function () {
339
- isMounted.current = true;
340
- return function () {
341
- isMounted.current = false;
342
- };
343
- }, []);
344
- /**
345
- * Check if value is a primitive type (including null, Date)
346
- */
347
- var isPrimitive = useCallback(function (val) {
348
- if (val === null)
349
- return true;
350
- var type = typeof val;
351
- // ES5-compatible array check
352
- var primitiveTypes = ['string', 'number', 'boolean', 'bigint'];
353
- if (primitiveTypes.indexOf(type) !== -1)
354
- return true;
355
- // Date is special: treat as primitive and convert to ISO string
356
- if (val instanceof Date)
357
- return true;
358
- return false;
359
- }, []);
360
- /**
361
- * Serialize content for storage
362
- * - Primitives → String(content)
363
- * - Date → ISO string
364
- * - Objects/arrays → JSON.stringify()
365
- */
366
- var serializeValue = useCallback(function (content) {
367
- if (isPrimitive(content)) {
368
- return content instanceof Date ? content.toISOString() : String(content);
369
- }
370
- return JSON.stringify(content);
371
- }, [isPrimitive]);
124
+ const [data, setData] = useState(undefined);
125
+ const [description, setDescription] = useState(undefined);
126
+ const [isLoading, setIsLoading] = useState(false);
127
+ const [error, setError] = useState(undefined);
372
128
  /**
373
129
  * Deserialize value from storage
374
130
  * - Try JSON.parse first
375
131
  * - If fails, return raw string (will be cast to T by TypeScript)
376
132
  */
377
- var deserializeValue = useCallback(function (rawValue) {
133
+ const deserializeValue = useCallback((rawValue) => {
378
134
  try {
379
135
  return JSON.parse(rawValue);
380
136
  }
381
- catch (_a) {
137
+ catch {
382
138
  // Not valid JSON, assume it's a primitive value
383
139
  return rawValue;
384
140
  }
385
141
  }, []);
386
- /**
387
- * Discover tenant app catalog URL
388
- */
389
- var discoverAppCatalogUrl = useCallback(function () { return __awaiter(_this, void 0, void 0, function () {
390
- var response, data_1, err_1;
391
- return __generator(this, function (_a) {
392
- switch (_a.label) {
393
- case 0:
394
- if (!spHttpClient || !pageContext) {
395
- throw new Error('SPHttpClient or PageContext not available');
396
- }
397
- // Check cache first
398
- if (appCatalogUrl) {
399
- return [2 /*return*/, appCatalogUrl];
400
- }
401
- _a.label = 1;
402
- case 1:
403
- _a.trys.push([1, 4, , 5]);
404
- return [4 /*yield*/, spHttpClient.get("".concat(pageContext.web.absoluteUrl, "/_api/SP_TenantSettings_Current"), SPHttpClient.configurations.v1)];
405
- case 2:
406
- response = _a.sent();
407
- if (!response.ok) {
408
- throw new Error("Failed to discover app catalog: ".concat(response.statusText));
409
- }
410
- return [4 /*yield*/, response.json()];
411
- case 3:
412
- data_1 = _a.sent();
413
- if (!data_1.CorporateCatalogUrl) {
414
- throw new Error('Tenant app catalog is not provisioned. Please provision the app catalog first.');
415
- }
416
- if (isMounted.current) {
417
- setAppCatalogUrl(data_1.CorporateCatalogUrl);
418
- }
419
- return [2 /*return*/, data_1.CorporateCatalogUrl];
420
- case 4:
421
- err_1 = _a.sent();
422
- throw new Error("App catalog discovery failed: ".concat(err_1 instanceof Error ? err_1.message : String(err_1)));
423
- case 5: return [2 /*return*/];
424
- }
425
- });
426
- }); }, [spHttpClient, pageContext, appCatalogUrl]);
427
- /**
428
- * Check write permissions on tenant app catalog
429
- * User must be Site Collection Administrator of the tenant app catalog
430
- */
431
- var checkWritePermission = useCallback(function (catalogUrl) { return __awaiter(_this, void 0, void 0, function () {
432
- var response, user, _a;
433
- return __generator(this, function (_b) {
434
- switch (_b.label) {
435
- case 0:
436
- if (!spHttpClient)
437
- return [2 /*return*/, false];
438
- _b.label = 1;
439
- case 1:
440
- _b.trys.push([1, 4, , 5]);
441
- return [4 /*yield*/, spHttpClient.get("".concat(catalogUrl, "/_api/web/currentuser?$select=IsSiteAdmin"), SPHttpClient.configurations.v1)];
442
- case 2:
443
- response = _b.sent();
444
- if (!response.ok)
445
- return [2 /*return*/, false];
446
- return [4 /*yield*/, response.json()];
447
- case 3:
448
- user = _b.sent();
449
- return [2 /*return*/, user.IsSiteAdmin === true];
450
- case 4:
451
- _a = _b.sent();
452
- return [2 /*return*/, false];
453
- case 5: return [2 /*return*/];
454
- }
455
- });
456
- }); }, [spHttpClient]);
457
142
  /**
458
143
  * Load property from tenant app catalog
459
144
  */
460
- var load = useCallback(function () { return __awaiter(_this, void 0, void 0, function () {
461
- var catalogUrl, response, entity, err_2, error_1;
462
- return __generator(this, function (_a) {
463
- switch (_a.label) {
464
- case 0:
465
- if (!spHttpClient || !pageContext) {
466
- console.warn('SPHttpClient or PageContext not available yet. Skipping load.');
467
- return [2 /*return*/];
468
- }
469
- if (!key) {
470
- console.warn('key is required. Skipping load.');
471
- return [2 /*return*/];
472
- }
473
- setIsLoading(true);
474
- setError(undefined);
475
- _a.label = 1;
476
- case 1:
477
- _a.trys.push([1, 5, 6, 7]);
478
- return [4 /*yield*/, discoverAppCatalogUrl()];
479
- case 2:
480
- catalogUrl = _a.sent();
481
- // Check write permissions (async, non-blocking)
482
- checkWritePermission(catalogUrl).then(function (hasPermission) {
483
- if (isMounted.current) {
484
- setCanWrite(hasPermission);
485
- }
486
- }).catch(function () {
487
- if (isMounted.current) {
488
- setCanWrite(false);
489
- }
490
- });
491
- return [4 /*yield*/, spHttpClient.get("".concat(catalogUrl, "/_api/web/GetStorageEntity('").concat(encodeURIComponent(key), "')"), SPHttpClient.configurations.v1)];
492
- case 3:
493
- response = _a.sent();
494
- if (!response.ok) {
495
- throw new Error("Failed to read property: ".concat(response.statusText));
496
- }
497
- return [4 /*yield*/, response.json()];
498
- case 4:
499
- entity = _a.sent();
500
- if (isMounted.current) {
501
- if (entity.Value) {
502
- setData(deserializeValue(entity.Value));
503
- setDescription(entity.Description);
504
- }
505
- else {
506
- // Property doesn't exist or has no value
507
- setData(undefined);
508
- setDescription(undefined);
509
- }
510
- }
511
- return [3 /*break*/, 7];
512
- case 5:
513
- err_2 = _a.sent();
514
- if (isMounted.current) {
515
- error_1 = err_2 instanceof Error ? err_2 : new Error(String(err_2));
516
- setError(error_1);
517
- console.error('Failed to load tenant property:', error_1);
518
- }
519
- return [3 /*break*/, 7];
520
- case 6:
521
- if (isMounted.current) {
522
- setIsLoading(false);
523
- }
524
- return [7 /*endfinally*/];
525
- case 7: return [2 /*return*/];
145
+ const load = useCallback(async () => {
146
+ if (!spHttpClient) {
147
+ console.warn('SPHttpClient not available yet. Skipping load.');
148
+ return;
149
+ }
150
+ if (!key) {
151
+ console.warn('key is required. Skipping load.');
152
+ return;
153
+ }
154
+ setIsLoading(true);
155
+ setError(undefined);
156
+ try {
157
+ // Discover app catalog URL
158
+ const catalogUrl = await discoverAppCatalogUrl();
159
+ // Read property
160
+ const response = await spHttpClient.get(`${catalogUrl}/_api/web/GetStorageEntity('${encodeURIComponent(key)}')`, SPHttpClient.configurations.v1);
161
+ if (!response.ok) {
162
+ throw new Error(`Failed to read property: ${response.statusText}`);
526
163
  }
527
- });
528
- }); }, [spHttpClient, pageContext, key, discoverAppCatalogUrl, checkWritePermission, deserializeValue]);
529
- /**
530
- * Write property to tenant app catalog
531
- */
532
- var write = useCallback(function (content, desc) { return __awaiter(_this, void 0, void 0, function () {
533
- var catalogUrl, serializedValue, body, response, errorText, err_3, error_2;
534
- return __generator(this, function (_a) {
535
- switch (_a.label) {
536
- case 0:
537
- if (!spHttpClient || !pageContext) {
538
- throw new Error('SPHttpClient or PageContext not available. Cannot write property.');
539
- }
540
- if (!key) {
541
- throw new Error('key is required. Cannot write property.');
542
- }
543
- setIsWriting(true);
544
- setWriteError(undefined);
545
- _a.label = 1;
546
- case 1:
547
- _a.trys.push([1, 6, 7, 8]);
548
- return [4 /*yield*/, discoverAppCatalogUrl()];
549
- case 2:
550
- catalogUrl = _a.sent();
551
- serializedValue = serializeValue(content);
552
- body = {
553
- key: key,
554
- value: serializedValue,
555
- description: desc !== null && desc !== void 0 ? desc : ''
556
- // comment is NOT supported by SharePoint SetStorageEntity API
557
- };
558
- return [4 /*yield*/, spHttpClient.post("".concat(catalogUrl, "/_api/web/SetStorageEntity"), SPHttpClient.configurations.v1, {
559
- headers: {
560
- 'Content-Type': 'application/json;odata=verbose',
561
- 'Accept': 'application/json;odata=verbose'
562
- },
563
- body: JSON.stringify(body)
564
- })];
565
- case 3:
566
- response = _a.sent();
567
- if (!!response.ok) return [3 /*break*/, 5];
568
- return [4 /*yield*/, response.text()];
569
- case 4:
570
- errorText = _a.sent();
571
- throw new Error("Failed to write property: ".concat(response.statusText, ". ").concat(errorText));
572
- case 5:
573
- if (isMounted.current) {
574
- // Update local state to reflect successful write
575
- setData(content);
576
- setDescription(desc);
577
- // Clear read error if write succeeds
578
- setError(undefined);
579
- }
580
- return [3 /*break*/, 8];
581
- case 6:
582
- err_3 = _a.sent();
583
- if (isMounted.current) {
584
- error_2 = err_3 instanceof Error ? err_3 : new Error(String(err_3));
585
- setWriteError(error_2);
586
- console.error('Failed to write tenant property:', error_2);
587
- }
588
- // Re-throw to allow caller to handle
589
- throw err_3;
590
- case 7:
591
- if (isMounted.current) {
592
- setIsWriting(false);
593
- }
594
- return [7 /*endfinally*/];
595
- case 8: return [2 /*return*/];
164
+ const entity = await response.json();
165
+ if (isMountedRef.current) {
166
+ if (entity.Value) {
167
+ setData(deserializeValue(entity.Value));
168
+ setDescription(entity.Description);
169
+ }
170
+ else {
171
+ // Property doesn't exist or has no value
172
+ setData(undefined);
173
+ setDescription(undefined);
174
+ }
596
175
  }
597
- });
598
- }); }, [spHttpClient, pageContext, key, discoverAppCatalogUrl, serializeValue]);
599
- /**
600
- * Remove property from tenant app catalog
601
- */
602
- var remove = useCallback(function () { return __awaiter(_this, void 0, void 0, function () {
603
- var catalogUrl, response, errorText, err_4, error_3;
604
- return __generator(this, function (_a) {
605
- switch (_a.label) {
606
- case 0:
607
- if (!spHttpClient || !pageContext) {
608
- throw new Error('SPHttpClient or PageContext not available. Cannot remove property.');
609
- }
610
- if (!key) {
611
- throw new Error('key is required. Cannot remove property.');
612
- }
613
- setIsWriting(true);
614
- setWriteError(undefined);
615
- _a.label = 1;
616
- case 1:
617
- _a.trys.push([1, 6, 7, 8]);
618
- return [4 /*yield*/, discoverAppCatalogUrl()];
619
- case 2:
620
- catalogUrl = _a.sent();
621
- return [4 /*yield*/, spHttpClient.post("".concat(catalogUrl, "/_api/web/RemoveStorageEntity('").concat(encodeURIComponent(key), "')"), SPHttpClient.configurations.v1, {
622
- headers: {
623
- 'Accept': 'application/json;odata=verbose'
624
- }
625
- })];
626
- case 3:
627
- response = _a.sent();
628
- if (!!response.ok) return [3 /*break*/, 5];
629
- return [4 /*yield*/, response.text()];
630
- case 4:
631
- errorText = _a.sent();
632
- throw new Error("Failed to remove property: ".concat(response.statusText, ". ").concat(errorText));
633
- case 5:
634
- if (isMounted.current) {
635
- // Clear local state
636
- setData(undefined);
637
- setDescription(undefined);
638
- setError(undefined);
639
- }
640
- return [3 /*break*/, 8];
641
- case 6:
642
- err_4 = _a.sent();
643
- if (isMounted.current) {
644
- error_3 = err_4 instanceof Error ? err_4 : new Error(String(err_4));
645
- setWriteError(error_3);
646
- console.error('Failed to remove tenant property:', error_3);
647
- }
648
- // Re-throw to allow caller to handle
649
- throw err_4;
650
- case 7:
651
- if (isMounted.current) {
652
- setIsWriting(false);
653
- }
654
- return [7 /*endfinally*/];
655
- case 8: return [2 /*return*/];
176
+ }
177
+ catch (err) {
178
+ if (isMountedRef.current) {
179
+ const capturedError = err instanceof Error ? err : new Error(String(err));
180
+ setError(capturedError);
181
+ console.error('Failed to load tenant property:', capturedError);
182
+ }
183
+ }
184
+ finally {
185
+ if (isMountedRef.current) {
186
+ setIsLoading(false);
656
187
  }
657
- });
658
- }); }, [spHttpClient, pageContext, key, discoverAppCatalogUrl]);
188
+ }
189
+ }, [spHttpClient, key, discoverAppCatalogUrl, deserializeValue, isMountedRef]);
659
190
  // Auto-fetch on mount if enabled
660
- useEffect(function () {
661
- if (autoFetch && spHttpClient && pageContext && key) {
662
- load().catch(function () {
191
+ useEffect(() => {
192
+ if (autoFetch && spHttpClient && key) {
193
+ load().catch(() => {
663
194
  // Error already handled in load() function
664
195
  });
665
196
  }
666
- }, [autoFetch, spHttpClient, pageContext, key, load]);
197
+ }, [autoFetch, spHttpClient, key, load]);
667
198
  // Computed state: ready when data loaded successfully
668
- var isReady = !isLoading && !error && data !== undefined;
199
+ const isReady = !isLoading && !error && data !== undefined;
669
200
  return {
670
- data: data,
671
- description: description,
672
- isLoading: isLoading,
673
- error: error,
674
- isWriting: isWriting,
675
- writeError: writeError,
676
- canWrite: canWrite,
677
- load: load,
678
- write: write,
679
- remove: remove,
680
- isReady: isReady,
201
+ data,
202
+ description,
203
+ isLoading,
204
+ error,
205
+ load,
206
+ isReady,
681
207
  };
682
208
  }
683
209
  //# sourceMappingURL=useSPFxTenantProperty.js.map