Skip to content

Commit 80c35b8

Browse files
committed
feat: manage license dialog and license registration initial
1 parent 0739fb5 commit 80c35b8

File tree

4 files changed

+558
-2
lines changed

4 files changed

+558
-2
lines changed

src/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"app_name_about": "Phoenix Code",
55
"main_pro_plan": "Phoenix Pro",
66
"about_icon": "styles/images/phoenix-icon.svg",
7-
"account_url": "https://account.phcode.dev/",
7+
"account_url": "http://localhost:5000/",
88
"promotions_url": "https://promotions.phcode.dev/dev/",
99
"purchase_url": "https://phcode.io/pricing",
1010
"how_to_use_url": "https://github.com/adobe/brackets/wiki/How-to-Use-Brackets",
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<div class="license-management-dialog modal">
2+
<div class="modal-header">
3+
<h1 class="dialog-title">Manage Device License</h1>
4+
</div>
5+
6+
<div class="modal-body" style="max-height: 500px; padding: 24px;">
7+
<!-- License Activation Section -->
8+
<div class="license-activation-section">
9+
<div class="license-form-group">
10+
<label for="license-key-input" class="license-form-label">
11+
License Key
12+
</label>
13+
<input
14+
type="text"
15+
id="license-key-input"
16+
class="license-form-input"
17+
placeholder="Enter your license key..."
18+
/>
19+
</div>
20+
<button
21+
id="activate-license-btn"
22+
class="btn primary license-activate-btn">
23+
<span class="btn-text">Activate License</span>
24+
<span class="btn-spinner">
25+
<i class="fa fa-spinner fa-spin" style="margin-right: 8px;"></i>Activating...
26+
</span>
27+
</button>
28+
</div>
29+
30+
<!-- Divider -->
31+
<hr class="license-divider">
32+
33+
<!-- License Status Section -->
34+
<div class="license-status-section">
35+
<h3 class="license-section-title">
36+
Current Device License
37+
</h3>
38+
39+
<!-- Loading State -->
40+
<div id="license-status-loading" class="license-status-loading">
41+
<i class="fa fa-spinner fa-spin"></i>
42+
<span>Checking license status...</span>
43+
</div>
44+
45+
<!-- No License State -->
46+
<div id="license-status-none" class="license-status-none" style="display: none;">
47+
<i class="fa fa-exclamation-circle"></i>
48+
<span>No active device license found</span>
49+
</div>
50+
51+
<!-- Valid License State -->
52+
<div id="license-status-valid" class="license-status-valid" style="display: none;">
53+
<div class="license-info-card">
54+
<div class="license-info-row" style="margin-bottom: 12px;">
55+
<span class="license-info-label">Status:</span>
56+
<span class="license-status-badge">Active</span>
57+
</div>
58+
<div class="license-info-row">
59+
<span class="license-info-label">Licensed to:</span>
60+
<span id="licensed-to-name" class="license-info-value"></span>
61+
</div>
62+
<div class="license-info-row">
63+
<span class="license-info-label">License type:</span>
64+
<span id="license-type-name" class="license-info-value"></span>
65+
</div>
66+
<div class="license-info-row">
67+
<span class="license-info-label">Valid until:</span>
68+
<span id="license-valid-till" class="license-info-value"></span>
69+
</div>
70+
</div>
71+
</div>
72+
73+
<!-- Error State -->
74+
<div id="license-status-error" class="license-status-error" style="display: none;">
75+
<i class="fa fa-exclamation-triangle"></i>
76+
<span id="license-error-message">Error checking license status</span>
77+
</div>
78+
</div>
79+
80+
<!-- Success/Error Messages for Activation -->
81+
<div id="activation-message" class="license-activation-message">
82+
<span id="activation-message-text"></span>
83+
</div>
84+
</div>
85+
86+
<div class="modal-footer">
87+
<button class="dialog-button btn" data-button-id="close">Close</button>
88+
</div>
89+
</div>

src/services/manage-licenses.js

Lines changed: 241 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)