Skip to content

Commit cc3bc7c

Browse files
mcollovatimshabarov
andcommitted
feat: try to download license key when log in from splash screen (#21981)
* feat: try to download license key when log in from splash screen When pressing the login button on the splash screen, redirects the user to the vaadin.com license validation endpoint and polls license server to download a license key. If the license key is available the application browser page gets reloaded. Otherwise an error message is shown. * Apply suggestions from code review --------- Co-authored-by: Mikhail Shabarov <61410877+mshabarov@users.noreply.github.com>
1 parent 384bbbb commit cc3bc7c

File tree

6 files changed

+155
-10
lines changed

6 files changed

+155
-10
lines changed

flow-test-generic/src/main/java/com/vaadin/flow/testutil/ClassesSerializableTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ protected Stream<String> getExcludedPatterns() {
7878
"com\\.vaadin\\.base\\.devserver\\.BrowserLauncher",
7979
"com\\.vaadin\\.base\\.devserver\\.BrowserLiveReloadAccessorImpl",
8080
"com\\.vaadin\\.base\\.devserver\\.DebugWindowConnection",
81+
"com\\.vaadin\\.base\\.devserver\\.DebugWindowConnection\\$LicenseDownloadCallback",
8182
"com\\.vaadin\\.base\\.devserver\\.DebugWindowConnection\\$DevToolsInterfaceImpl",
8283
"com\\.vaadin\\.base\\.devserver\\.DevModeHandlerManagerImpl",
8384
"com\\.vaadin\\.base\\.devserver\\.DevServerWatchDog",

vaadin-dev-server/src/main/frontend/License.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { ServerMessage } from './vaadin-dev-tools';
22
import {
33
preTrialStartFailed,
4-
showPreTrialSplashScreen
4+
showPreTrialSplashScreen,
5+
updateLicenseDownloadStatus
56
} from './pre-trial-splash-screen';
67

78
const noLicenseFallbackTimeout = 1000;
@@ -175,10 +176,30 @@ export const handleLicenseMessage = (message: ServerMessage, bodyShadowRoot: Sha
175176
console.debug('Pre-trial period start failed', message.data);
176177
preTrialStartFailed(false, bodyShadowRoot);
177178
return true;
179+
} else if (message.command === 'license-download-completed') {
180+
console.debug('License downloaded');
181+
window.location.reload();
182+
return true;
183+
} else if (message.command === 'license-download-started') {
184+
updateLicenseDownloadStatus('started', bodyShadowRoot);
185+
return true;
186+
} else if (message.command === 'license-download-failed') {
187+
updateLicenseDownloadStatus('failed', bodyShadowRoot);
188+
return true;
178189
}
179190
return false;
180191
};
181192

193+
export const startPreTrial = () => {
194+
(window as any).Vaadin.devTools.startPreTrial();
195+
};
196+
export const tryAcquireLicense = () => {
197+
const products = Object.values(productMissingLicense);
198+
if (products.length > 0) {
199+
(window as any).Vaadin.devTools.downloadLicense(products[0].product);
200+
}
201+
};
202+
182203
export const licenseInit = () => {
183204
// Process already registered elements
184205
(window as any).Vaadin.devTools.createdCvdlElements.forEach((element: Element) => {

vaadin-dev-server/src/main/frontend/pre-trial-splash-screen.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { ProductAndMessage } from './License';
1+
import { ProductAndMessage, startPreTrial, tryAcquireLicense } from './License';
22

33
class PreTrial extends HTMLElement {
44
#parentObserver: MutationObserver | null;
55
#shadowRoot: ShadowRoot;
66
#trialExpired: boolean;
77
#startFailed: boolean | null;
8+
#licenseDownloadStatus: string | null;
89
private remove: Function;
910

1011
constructor() {
@@ -13,6 +14,7 @@ class PreTrial extends HTMLElement {
1314
this.#parentObserver = null;
1415
this.#trialExpired = false;
1516
this.#startFailed = null;
17+
this.#licenseDownloadStatus = null;
1618

1719
// Create a shadow DOM for encapsulation
1820
this.#shadowRoot = this.attachShadow({ mode: 'closed' });
@@ -24,7 +26,7 @@ class PreTrial extends HTMLElement {
2426

2527
// Define the observed attributes for the web component
2628
static get observedAttributes(): string[] {
27-
return ['expired', 'start-failure'];
29+
return ['expired', 'start-failure', 'license-download'];
2830
}
2931

3032
private render(): void {
@@ -154,9 +156,11 @@ class PreTrial extends HTMLElement {
154156
<p>
155157
This trial includes all commercial tools and components. Production builds during the trial will be watermarked.
156158
</p>
159+
${this.#licenseDownloadStatus === 'started' ? '<p><strong>Waiting for the license key to be downloaded...</strong></p>' : ''}
160+
${this.#licenseDownloadStatus === 'failed' ? '<div class="error">Failed to download the license key. Please try again later.</div>' : ''}
157161
<div class='button-container'>
158-
<button class='action-button'>${this.#trialExpired ? 'Extend trial 30 days' : 'Try for 7 days'}</button>
159-
<button class='login-button'>
162+
<button ${this.#licenseDownloadStatus === 'started' ? 'disabled' : ''} class='action-button'>${this.#trialExpired ? 'Extend trial 30 days' : 'Try for 7 days'}</button>
163+
<button ${this.#licenseDownloadStatus === 'started' ? 'disabled' : ''} class='login-button'>
160164
Log in / Sign up
161165
<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'>
162166
<path fill='#444' d='M14 16v-11l-1 1v9h-12v-12h9l1-1h-11v14z'></path>
@@ -172,12 +176,12 @@ class PreTrial extends HTMLElement {
172176
if (this.#trialExpired) {
173177
this.openNewWindow('https://vaadin.com/pricing');
174178
} else {
175-
(window as any).Vaadin.devTools.startPreTrial();
179+
startPreTrial();
176180
}
177181
});
178182
const loginButton = this.#shadowRoot.querySelector('button.login-button')!;
179183
loginButton.addEventListener('click', () => {
180-
this.openNewWindow('https://vaadin.com/my/account');
184+
tryAcquireLicense();
181185
});
182186
}
183187

@@ -201,6 +205,15 @@ class PreTrial extends HTMLElement {
201205
this.handleExpiredChange(newValue !== null && newValue !== 'false');
202206
} else if (name === 'start-failure') {
203207
this.handleStartFailed(newValue === 'expired');
208+
} else if (name === 'license-download') {
209+
this.handleLicenseDownload(newValue);
210+
}
211+
}
212+
213+
private handleLicenseDownload(value: string | null) {
214+
if (this.#licenseDownloadStatus !== value) {
215+
this.#licenseDownloadStatus = value;
216+
this.render();
204217
}
205218
}
206219

@@ -325,3 +338,9 @@ export const preTrialStartFailed = (expired: boolean, shadowRoot: ShadowRoot | n
325338
element?.setAttribute('start-failure', expired ? 'expired' : '');
326339
}
327340
};
341+
export const updateLicenseDownloadStatus = (action: 'started' | 'failed' | 'completed', shadowRoot: ShadowRoot | null) => {
342+
if (shadowRoot) {
343+
const element = shadowRoot.querySelector('vaadin-pretrial');
344+
element?.setAttribute('license-download', action);
345+
}
346+
};

vaadin-dev-server/src/main/frontend/vaadin-dev-tools.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ import {
55
handleLicenseMessage,
66
licenseCheckFailed,
77
licenseInit,
8-
handlePreTrialMessage
8+
handlePreTrialMessage, licenseDownloadFailed
99
} from './License';
1010
import { ConnectionStatus } from './connection';
1111
import { LiveReloadConnection } from './live-reload-connection';
1212
import { WebSocketConnection } from './websocket-connection';
13-
import { preTrialStartFailed } from './pre-trial-splash-screen';
13+
import {
14+
preTrialStartFailed,
15+
updateLicenseDownloadStatus
16+
} from './pre-trial-splash-screen';
1417

1518
/**
1619
* Plugin API for the dev tools window.
@@ -840,6 +843,13 @@ export class VaadinDevTools extends LitElement {
840843
preTrialStartFailed(false, this.bodyShadowRoot)
841844
}
842845
}
846+
downloadLicense(productInfo: Product) {
847+
if (this.frontendConnection) {
848+
this.frontendConnection.send('downloadLicense', productInfo);
849+
} else {
850+
updateLicenseDownloadStatus('failed', this.bodyShadowRoot);
851+
}
852+
}
843853

844854
setActive(yes: boolean) {
845855
this.frontendConnection?.setActive(yes);

vaadin-dev-server/src/main/java/com/vaadin/base/devserver/DebugWindowConnection.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,8 @@ public void onMessage(AtmosphereResource resource, String message) {
324324
data.get("enabled").booleanValue());
325325
} else if ("reportTelemetry".equals(command)) {
326326
DevModeUsageStatistics.handleBrowserData(data);
327+
} else if ("downloadLicense".equals(command)) {
328+
handleLicenseKeyDownload(resource, data);
327329
} else if ("checkLicense".equals(command)) {
328330
handleLicenseCheck(resource, data);
329331
} else if ("startPreTrialLicense".equals(command)) {
@@ -374,6 +376,18 @@ private void handleLicenseCheck(AtmosphereResource resource,
374376
}
375377
}
376378

379+
private void handleLicenseKeyDownload(AtmosphereResource resource,
380+
JsonNode data) {
381+
String name = data.get("name").textValue();
382+
String version = data.get("version").textValue();
383+
Product product = new Product(name, version);
384+
385+
LicenseChecker.checkLicenseAsync(product.getName(),
386+
product.getVersion(), BuildType.DEVELOPMENT,
387+
new LicenseDownloadCallback(resource, product));
388+
send(resource, "license-download-started", product);
389+
}
390+
377391
private void handlePreTrialStart(AtmosphereResource resource,
378392
JsonNode data) {
379393
try {
@@ -431,4 +445,24 @@ public void sendHmrEvent(String event, JsonNode eventData) {
431445
broadcast(msg);
432446
}
433447

448+
private class LicenseDownloadCallback implements LicenseChecker.Callback {
449+
private final AtmosphereResource resource;
450+
private final Product product;
451+
452+
public LicenseDownloadCallback(AtmosphereResource resource,
453+
Product product) {
454+
this.resource = resource;
455+
this.product = product;
456+
}
457+
458+
@Override
459+
public void ok() {
460+
send(resource, "license-download-completed", product);
461+
}
462+
463+
@Override
464+
public void failed(Exception e) {
465+
send(resource, "license-download-failed", product);
466+
}
467+
}
434468
}

vaadin-dev-server/src/test/java/com/vaadin/base/devserver/DebugWindowConnectionLicenseCheckTest.java

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.util.ArrayList;
1919
import java.util.List;
20+
import java.util.concurrent.atomic.AtomicReference;
2021
import java.util.function.Consumer;
2122

2223
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -42,7 +43,9 @@
4243
import com.vaadin.pro.licensechecker.PreTrialLicenseValidationException;
4344
import com.vaadin.pro.licensechecker.Product;
4445

46+
import static org.mockito.ArgumentMatchers.any;
4547
import static org.mockito.ArgumentMatchers.anyString;
48+
import static org.mockito.ArgumentMatchers.eq;
4649
import static org.mockito.ArgumentMatchers.same;
4750

4851
public class DebugWindowConnectionLicenseCheckTest {
@@ -151,6 +154,36 @@ public void startPreTrial_genericError_sendPreTrialNotAllowedFailure() {
151154
Assert.assertEquals("license-pretrial-failed", message.getCommand());
152155
}
153156

157+
@Test
158+
public void downloadLicenseKey_licenseKeyAvailable_notifiesDownloadStartAndSuccess() {
159+
AtomicReference<Runnable> callbackHolder = new AtomicReference<>();
160+
DebugWindowMessage message = doDownloadKey(true, callbackHolder);
161+
Assert.assertEquals("license-download-started", message.getCommand());
162+
Assert.assertEquals(TEST_PRODUCT.toString(),
163+
message.getData().toString());
164+
callbackHolder.get().run();
165+
Assert.assertEquals(1, receiver.messages.size());
166+
Assert.assertEquals("license-download-completed",
167+
receiver.messages.get(0).getCommand());
168+
Assert.assertEquals(TEST_PRODUCT.toString(),
169+
receiver.messages.get(0).getData().toString());
170+
}
171+
172+
@Test
173+
public void downloadLicenseKey_licenseKeyNotAvailable_notifiesDownloadStartAndFailure() {
174+
AtomicReference<Runnable> callbackHolder = new AtomicReference<>();
175+
DebugWindowMessage message = doDownloadKey(false, callbackHolder);
176+
Assert.assertEquals("license-download-started", message.getCommand());
177+
Assert.assertEquals(TEST_PRODUCT.toString(),
178+
message.getData().toString());
179+
callbackHolder.get().run();
180+
Assert.assertEquals(1, receiver.messages.size());
181+
Assert.assertEquals("license-download-failed",
182+
receiver.messages.get(0).getCommand());
183+
Assert.assertEquals(TEST_PRODUCT.toString(),
184+
receiver.messages.get(0).getData().toString());
185+
}
186+
154187
private enum LicenseCheckResult {
155188
VALID, INVALID, MISSING_KEYS
156189
}
@@ -183,6 +216,32 @@ private DebugWindowMessage doStartPreTrial(
183216
}));
184217
}
185218

219+
private DebugWindowMessage doDownloadKey(boolean success,
220+
AtomicReference<Runnable> callbackHolder) {
221+
ObjectNode command = OBJECT_MAPPER.createObjectNode();
222+
command.put("command", "downloadLicense");
223+
command.putPOJO("data", TEST_PRODUCT);
224+
return sendAndReceive(command,
225+
licenseChecker -> licenseChecker.when(() -> LicenseChecker
226+
.checkLicenseAsync(eq(TEST_PRODUCT.getName()),
227+
eq(TEST_PRODUCT.getVersion()),
228+
eq(BuildType.DEVELOPMENT),
229+
any(LicenseChecker.Callback.class)))
230+
.then(i -> {
231+
LicenseChecker.Callback callback = i.getArgument(3,
232+
LicenseChecker.Callback.class);
233+
callbackHolder.set(() -> {
234+
if (success) {
235+
callback.ok();
236+
} else {
237+
callback.failed(
238+
new LicenseException("BOOM"));
239+
}
240+
});
241+
return null;
242+
}));
243+
}
244+
186245
private DebugWindowMessage sendAndReceive(ObjectNode command,
187246
Consumer<MockedStatic<LicenseChecker>> instrumenter) {
188247
receiver.messages.clear();
@@ -259,7 +318,8 @@ private DebugWindowMessage deserializeMessage(String message)
259318
JsonNode json = OBJECT_MAPPER.readTree(message);
260319
String command = json.get("command").textValue();
261320
JsonNode data = json.get("data");
262-
if (command.startsWith("license-check-")) {
321+
if (command.startsWith("license-check-")
322+
|| command.startsWith("license-download-")) {
263323
if (data.has("message") && data.has("product")) {
264324
PreTrial preTrial = null;
265325
if (data.hasNonNull("preTrial")) {

0 commit comments

Comments
 (0)