@@ -146,11 +146,17 @@ export async function loadCachedLicenceState(
146146}
147147
148148/**
149- * Activate a licence key: verify cryptographically, persist state, and
150- * fire a background server notification .
149+ * Activate a licence key: verify cryptographically, check the server for
150+ * revocation (with a short timeout), persist state, and return the result .
151151 *
152- * Returns the resulting LicenceState immediately after local verification.
153- * The server POST happens in the background and does not block the result.
152+ * Flow:
153+ * 1. Verify Ed25519 signature locally (instant, offline).
154+ * 2. If valid, POST to the server with a 3-second timeout.
155+ * 3. If the server responds 403/404 (revoked/unknown), return invalid.
156+ * 4. If the server confirms (200) or is unreachable/errors, return valid.
157+ *
158+ * This ensures that revoked keys are caught immediately when online, while
159+ * offline activation still works via the cryptographic signature alone.
154160 */
155161export async function activateLicenceKey (
156162 key : string ,
@@ -171,16 +177,18 @@ export async function activateLicenceKey(
171177 return invalidLicenceState ( ) ;
172178 }
173179
180+ // Check with the server (short timeout -- don't block the user long).
181+ const revoked = await checkServerRevocationStatus ( trimmed , context ) ;
182+ if ( revoked ) {
183+ await clearPersistedState ( context . secrets ) ;
184+ return invalidLicenceState ( ) ;
185+ }
186+
174187 // Build and persist valid state.
175188 const product = result . product as LicenceProduct ;
176189 const state : LicenceState = { status : 'valid' , product } ;
177190 await persistLicenceState ( context . secrets , trimmed , state ) ;
178191
179- // Background server notification (non-blocking).
180- notifyServer ( trimmed , context ) . catch ( ( ) => {
181- // Silently ignore -- server activation is best-effort.
182- } ) ;
183-
184192 return state ;
185193}
186194
@@ -235,26 +243,44 @@ export async function checkServerForRevocation(
235243 }
236244}
237245
238- // ── Background server notification ─────── ──────────────────────────────────
246+ // ── Server revocation check at activation ──────────────────────────────────
239247
240- async function notifyServer (
248+ /**
249+ * POST to the server to check whether a key has been revoked.
250+ * Returns true if the server explicitly reports the key as revoked (403/404).
251+ * Returns false (not revoked) if the server confirms (200), is unreachable,
252+ * times out, or returns any other error -- offline-first, optimistic.
253+ *
254+ * Uses a short 3-second timeout so the user isn't blocked long.
255+ */
256+ async function checkServerRevocationStatus (
241257 key : string ,
242258 context : vscode . ExtensionContext ,
243- ) : Promise < void > {
244- const baseUrl = getBaseUrl ( ) ;
245- const response = await fetch ( `${ baseUrl } /api/v1/licence/activate` , {
246- method : 'POST' ,
247- headers : { 'Content-Type' : 'application/json' } ,
248- body : JSON . stringify ( {
249- licenceKey : key ,
250- deviceId : vscode . env . machineId ,
251- deviceInfo : `VS Code ${ vscode . version } / ${ getOsPlatformLabel ( ) } ` ,
252- } ) ,
253- signal : AbortSignal . timeout ( 10_000 ) ,
254- } ) ;
255-
256- if ( response . ok ) {
259+ ) : Promise < boolean > {
260+ try {
261+ const baseUrl = getBaseUrl ( ) ;
262+ const response = await fetch ( `${ baseUrl } /api/v1/licence/activate` , {
263+ method : 'POST' ,
264+ headers : { 'Content-Type' : 'application/json' } ,
265+ body : JSON . stringify ( {
266+ licenceKey : key ,
267+ deviceId : vscode . env . machineId ,
268+ deviceInfo : `VS Code ${ vscode . version } / ${ getOsPlatformLabel ( ) } ` ,
269+ } ) ,
270+ signal : AbortSignal . timeout ( 3_000 ) ,
271+ } ) ;
272+
273+ // Record successful server contact.
257274 await context . secrets . store ( SECRET_LAST_SERVER_CHECK , Date . now ( ) . toString ( ) ) ;
275+
276+ if ( response . status === 403 || response . status === 404 ) {
277+ return true ; // revoked
278+ }
279+
280+ return false ; // confirmed or unknown error -- not revoked
281+ } catch {
282+ // Unreachable / timeout -- not revoked (optimistic).
283+ return false ;
258284 }
259285}
260286
0 commit comments