@@ -32,6 +32,13 @@ define(function (require, exports, module) {
3232 throw new Error ( "manage-licenses should have access to KernalModeTrust. Cannot boot without trust ring" ) ;
3333 }
3434
35+ const Dialogs = require ( "widgets/Dialogs" ) ,
36+ Mustache = require ( "thirdparty/mustache/mustache" ) ,
37+ licenseManagementHTML = require ( "text!./html/license-management.html" ) ;
38+
39+ // Save a copy of window.fetch so that extensions won't tamper with it
40+ let fetchFn = window . fetch ;
41+
3542 async function _getLinuxDeviceID ( ) {
3643 const LINUX_DEVICE_ID_FILE = Phoenix . VFS . getTauriVirtualPath ( '/etc/machine-id' ) ;
3744 const result = await Phoenix . VFS . readFileResolves ( LINUX_DEVICE_ID_FILE , 'utf8' ) ;
@@ -53,8 +60,241 @@ define(function (require, exports, module) {
5360 }
5461 }
5562
63+ /**
64+ * Get the API base URL for license operations
65+ */
66+ function _getAPIBaseURL ( ) {
67+ return Phoenix . config . account_url . replace ( / \/ $ / , '' ) ; // Remove trailing slash
68+ }
69+
70+ /**
71+ * Call the validateDeviceLicense API
72+ */
73+ async function _validateDeviceLicense ( deviceLicenseKey ) {
74+ const apiURL = `${ _getAPIBaseURL ( ) } /validateDeviceLicense` ;
75+
76+ try {
77+ const response = await fetchFn ( apiURL , {
78+ method : 'POST' ,
79+ headers : {
80+ 'Content-Type' : 'application/json'
81+ } ,
82+ body : JSON . stringify ( {
83+ deviceLicenseKey : deviceLicenseKey
84+ } )
85+ } ) ;
86+
87+ if ( ! response . ok ) {
88+ throw new Error ( `HTTP ${ response . status } : ${ response . statusText } ` ) ;
89+ }
90+
91+ return await response . json ( ) ;
92+ } catch ( error ) {
93+ console . error ( 'Error validating device license:' , error ) ;
94+ throw error ;
95+ }
96+ }
97+
98+ /**
99+ * Call the registerDevice API to activate a license
100+ */
101+ async function _registerDevice ( licenseKey , deviceLicenseKey , platform , deviceLabel ) {
102+ const apiURL = `${ _getAPIBaseURL ( ) } /registerDevice` ;
103+
104+ try {
105+ const response = await fetchFn ( apiURL , {
106+ method : 'POST' ,
107+ headers : {
108+ 'Content-Type' : 'application/json' ,
109+ } ,
110+ body : JSON . stringify ( {
111+ licenseKey : licenseKey ,
112+ deviceLicenseKey : deviceLicenseKey ,
113+ platform : platform ,
114+ deviceLabel : deviceLabel
115+ } )
116+ } ) ;
117+
118+ const result = await response . json ( ) ;
119+
120+ if ( ! response . ok ) {
121+ throw new Error ( result . errorMessage || `HTTP ${ response . status } : ${ response . statusText } ` ) ;
122+ }
123+
124+ return result ;
125+ } catch ( error ) {
126+ console . error ( 'Error registering device:' , error ) ;
127+ throw error ;
128+ }
129+ }
130+
131+ /**
132+ * Format date for display
133+ */
134+ function _formatDate ( timestamp ) {
135+ if ( ! timestamp ) {
136+ return 'Never' ;
137+ }
138+ const date = new Date ( timestamp ) ;
139+ return date . toLocaleDateString ( 'en-US' , {
140+ year : 'numeric' ,
141+ month : 'long' ,
142+ day : 'numeric'
143+ } ) ;
144+ }
145+
146+ /**
147+ * Update the license status display in the dialog
148+ */
149+ function _updateLicenseStatusDisplay ( $dialog , licenseData ) {
150+ const $loading = $dialog . find ( '#license-status-loading' ) ;
151+ const $none = $dialog . find ( '#license-status-none' ) ;
152+ const $valid = $dialog . find ( '#license-status-valid' ) ;
153+ const $error = $dialog . find ( '#license-status-error' ) ;
154+
155+ // Hide all status sections
156+ $loading . hide ( ) ;
157+ $none . hide ( ) ;
158+ $valid . hide ( ) ;
159+ $error . hide ( ) ;
160+
161+ if ( licenseData && licenseData . isValid ) {
162+ // Show valid license info
163+ $dialog . find ( '#licensed-to-name' ) . text ( licenseData . licensedToName || 'Unknown' ) ;
164+ $dialog . find ( '#license-type-name' ) . text ( licenseData . licenseTypeName || 'Unknown' ) ;
165+ $dialog . find ( '#license-valid-till' ) . text ( _formatDate ( licenseData . validTill ) ) ;
166+ $valid . show ( ) ;
167+ } else if ( licenseData && licenseData . isValid === false ) {
168+ // No valid license
169+ $none . show ( ) ;
170+ } else {
171+ // Error state
172+ $dialog . find ( '#license-error-message' ) . text ( 'Error checking license status' ) ;
173+ $error . show ( ) ;
174+ }
175+ }
176+
177+ /**
178+ * Show activation result message
179+ */
180+ function _showActivationMessage ( $dialog , isSuccess , message ) {
181+ const $messageDiv = $dialog . find ( '#activation-message' ) ;
182+ const $messageText = $dialog . find ( '#activation-message-text' ) ;
183+
184+ $messageText . text ( message ) ;
185+
186+ // Remove previous classes
187+ $messageDiv . removeClass ( 'success error' ) ;
188+
189+ // Add appropriate class
190+ if ( isSuccess ) {
191+ $messageDiv . addClass ( 'success' ) ;
192+ } else {
193+ $messageDiv . addClass ( 'error' ) ;
194+ }
195+
196+ $messageDiv . show ( ) ;
197+
198+ // Hide message after 5 seconds
199+ setTimeout ( ( ) => {
200+ $messageDiv . fadeOut ( ) ;
201+ } , 5000 ) ;
202+ }
203+
204+ /**
205+ * Load and display current license status
206+ */
207+ async function _loadLicenseStatus ( $dialog ) {
208+ try {
209+ const deviceID = await _getDeviceID ( ) ;
210+ if ( ! deviceID ) {
211+ _updateLicenseStatusDisplay ( $dialog , { isValid : false } ) ;
212+ return ;
213+ }
214+
215+ const licenseData = await _validateDeviceLicense ( deviceID ) ;
216+ _updateLicenseStatusDisplay ( $dialog , licenseData ) ;
217+ } catch ( error ) {
218+ console . error ( 'Error loading license status:' , error ) ;
219+ _updateLicenseStatusDisplay ( $dialog , null ) ;
220+ }
221+ }
222+
223+ /**
224+ * Handle license activation
225+ */
226+ async function _handleLicenseActivation ( $dialog , licenseKey ) {
227+ const $btn = $dialog . find ( '#activate-license-btn' ) ;
228+ const $btnText = $btn . find ( '.btn-text' ) ;
229+ const $btnSpinner = $btn . find ( '.btn-spinner' ) ;
230+
231+ try {
232+ // Show loading state
233+ $btn . prop ( 'disabled' , true ) ;
234+ $btnText . hide ( ) ;
235+ $btnSpinner . show ( ) ;
236+
237+ const deviceID = await _getDeviceID ( ) ;
238+ if ( ! deviceID ) {
239+ throw new Error ( 'Unable to get device ID. Device licenses are only supported on desktop applications.' ) ;
240+ }
241+
242+ const platform = Phoenix . platform || 'unknown' ;
243+ const deviceLabel = `Phoenix Code - ${ platform } ` ;
244+
245+ const result = await _registerDevice ( licenseKey , deviceID , platform , deviceLabel ) ;
246+
247+ if ( result . isSuccess ) {
248+ _showActivationMessage ( $dialog , true , result . message || 'License activated successfully!' ) ;
249+
250+ // Clear the input field
251+ $dialog . find ( '#license-key-input' ) . val ( '' ) ;
252+
253+ // Refresh license status
254+ await _loadLicenseStatus ( $dialog ) ;
255+ } else {
256+ _showActivationMessage ( $dialog , false , result . errorMessage || 'Failed to activate license' ) ;
257+ }
258+ } catch ( error ) {
259+ _showActivationMessage ( $dialog , false , error . message || 'Failed to activate license' ) ;
260+ } finally {
261+ // Reset button state
262+ $btn . prop ( 'disabled' , false ) ;
263+ $btnText . show ( ) ;
264+ $btnSpinner . hide ( ) ;
265+ }
266+ }
267+
56268 async function showManageLicensesDialog ( ) {
57- alert ( `machine id is: ${ await _getDeviceID ( ) } ` ) ;
269+ const $template = $ ( Mustache . render ( licenseManagementHTML , { } ) ) ;
270+
271+ Dialogs . showModalDialogUsingTemplate ( $template ) ;
272+
273+ // Set up event handlers
274+ const $dialog = $template ;
275+ const $licenseInput = $dialog . find ( '#license-key-input' ) ;
276+ const $activateBtn = $dialog . find ( '#activate-license-btn' ) ;
277+
278+ // Handle activate button click
279+ $activateBtn . on ( 'click' , async function ( ) {
280+ const licenseKey = $licenseInput . val ( ) . trim ( ) ;
281+ if ( ! licenseKey ) {
282+ _showActivationMessage ( $dialog , false , 'Please enter a license key' ) ;
283+ return ;
284+ }
285+
286+ await _handleLicenseActivation ( $dialog , licenseKey ) ;
287+ } ) ;
288+
289+ // Handle Enter key in license input
290+ $licenseInput . on ( 'keypress' , function ( e ) {
291+ if ( e . which === 13 ) { // Enter key
292+ $activateBtn . click ( ) ;
293+ }
294+ } ) ;
295+
296+ // Load current license status
297+ await _loadLicenseStatus ( $dialog ) ;
58298 }
59299
60300 exports . showManageLicensesDialog = showManageLicensesDialog ;
0 commit comments