From b7da40b90c5431df07ea4eda3633316c325dfb96 Mon Sep 17 00:00:00 2001 From: James H Ball Date: Tue, 22 Apr 2025 22:01:32 +0100 Subject: [PATCH] Add license key checking and registration --- Source/CommonPluginEditor.cpp | 2 +- Source/CommonPluginEditor.h | 8 +- Source/MainComponent.cpp | 2 +- Source/PluginEditor.cpp | 7 +- .../LicenseRegistrationComponent.cpp | 188 ++++++++++++++++++ .../components/LicenseRegistrationComponent.h | 37 ++++ osci-render.jucer | 4 + sosci.jucer | 4 + 8 files changed, 246 insertions(+), 6 deletions(-) create mode 100644 Source/components/LicenseRegistrationComponent.cpp create mode 100644 Source/components/LicenseRegistrationComponent.h diff --git a/Source/CommonPluginEditor.cpp b/Source/CommonPluginEditor.cpp index aeb57d3..b435c49 100644 --- a/Source/CommonPluginEditor.cpp +++ b/Source/CommonPluginEditor.cpp @@ -35,7 +35,7 @@ CommonPluginEditor::CommonPluginEditor(CommonAudioProcessor& p, juce::String app } } - addAndMakeVisible(visualiser); + addChildComponent(visualiser); int width = std::any_cast(audioProcessor.getProperty("appWidth", defaultWidth)); int height = std::any_cast(audioProcessor.getProperty("appHeight", defaultHeight)); diff --git a/Source/CommonPluginEditor.h b/Source/CommonPluginEditor.h index a4f92c1..b2b5623 100644 --- a/Source/CommonPluginEditor.h +++ b/Source/CommonPluginEditor.h @@ -9,6 +9,7 @@ #include "components/SvgButton.h" #include "components/VolumeComponent.h" #include "components/DownloaderComponent.h" +#include "components/LicenseRegistrationComponent.h" class CommonPluginEditor : public juce::AudioProcessorEditor { public: @@ -68,11 +69,12 @@ public: VolumeComponent volume{audioProcessor}; std::unique_ptr chooser; - juce::MenuBarComponent menuBar; - - juce::TooltipWindow tooltipWindow{nullptr, 0}; + juce::MenuBarComponent menuBar; juce::TooltipWindow tooltipWindow{nullptr, 0}; juce::DropShadower tooltipDropShadow{juce::DropShadow(juce::Colours::black.withAlpha(0.5f), 6, {0,0})}; + LicenseRegistrationComponent licenseRegistration {audioProcessor, [this](bool success) { + visualiser.setVisible(success); + }}; bool usingNativeMenuBar = false; #if JUCE_LINUX diff --git a/Source/MainComponent.cpp b/Source/MainComponent.cpp index e64d099..dd26179 100644 --- a/Source/MainComponent.cpp +++ b/Source/MainComponent.cpp @@ -124,7 +124,7 @@ MainComponent::MainComponent(OscirenderAudioProcessor& p, OscirenderAudioProcess BooleanParameter* visualiserFullScreen = audioProcessor.visualiserParameters.visualiserFullScreen; pluginEditor.visualiser.setFullScreen(visualiserFullScreen->getBoolValue()); - addAndMakeVisible(pluginEditor.visualiser); + addChildComponent(pluginEditor.visualiser); pluginEditor.visualiser.setFullScreenCallback([this, visualiserFullScreen](FullScreenMode mode) { if (mode == FullScreenMode::TOGGLE) { visualiserFullScreen->setBoolValueNotifyingHost(!visualiserFullScreen->getBoolValue()); diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 3c619d3..7b21833 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -77,7 +77,7 @@ OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioPr addAndMakeVisible(lua); addAndMakeVisible(luaResizerBar); - addAndMakeVisible(visualiser); + addChildComponent(visualiser); visualiser.openSettings = [this] { openVisualiserSettings(); @@ -95,6 +95,9 @@ OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioPr #endif initialiseMenuBar(model); + + addAndMakeVisible(licenseRegistration); + licenseRegistration.toFront(true); } OscirenderAudioProcessorEditor::~OscirenderAudioProcessorEditor() { @@ -183,6 +186,8 @@ void OscirenderAudioProcessorEditor::resized() { CommonPluginEditor::resized(); auto area = getLocalBounds(); + + licenseRegistration.setBounds(area); if (audioProcessor.visualiserParameters.visualiserFullScreen->getBoolValue()) { visualiser.setBounds(area); diff --git a/Source/components/LicenseRegistrationComponent.cpp b/Source/components/LicenseRegistrationComponent.cpp new file mode 100644 index 0000000..6c1f4b4 --- /dev/null +++ b/Source/components/LicenseRegistrationComponent.cpp @@ -0,0 +1,188 @@ +#include "LicenseRegistrationComponent.h" + +LicenseRegistrationComponent::LicenseRegistrationComponent(CommonAudioProcessor& processor, std::function onLicenseVerified) + : audioProcessor(processor), onLicenseVerified(onLicenseVerified) +{ + setupComponents(); + + // If we have a saved license, validate it in the background + if (validateSavedLicense()) + { + // Use Timer to ensure component is properly initialized before hiding + juce::MessageManager::callAsync([this]() { + setVisible(false); + }); + // Start periodic checks every hour + startTimer(1000 * 60 * 60); + } +} + +LicenseRegistrationComponent::~LicenseRegistrationComponent() +{ + stopTimer(); +} + +void LicenseRegistrationComponent::setupComponents() +{ + titleLabel.setText("License Registration Required", juce::dontSendNotification); + titleLabel.setFont(juce::Font(24.0f, juce::Font::bold)); + titleLabel.setJustificationType(juce::Justification::centred); + addAndMakeVisible(titleLabel); + + instructionsLabel.setText("Please enter your license key to continue", juce::dontSendNotification); + instructionsLabel.setJustificationType(juce::Justification::centred); + instructionsLabel.setFont(juce::Font(18.0f)); + addAndMakeVisible(instructionsLabel); + + licenseKeyEditor.setTextToShowWhenEmpty("XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX", juce::Colours::grey); + licenseKeyEditor.setInputRestrictions(35, "0123456789ABCDEF-"); // Only allow hex digits and hyphens + licenseKeyEditor.onReturnKey = [this] { verifyButton.triggerClick(); }; + licenseKeyEditor.setFont(juce::Font(24.0f)); + addAndMakeVisible(licenseKeyEditor); + + verifyButton.setButtonText("Verify License"); + verifyButton.onClick = [this] { + if (!isVerifying) + { + verifyLicense(licenseKeyEditor.getText()); + } + }; + addAndMakeVisible(verifyButton); +} + +void LicenseRegistrationComponent::paint(juce::Graphics& g) +{ + g.fillAll(juce::Colours::black.withAlpha(0.9f)); + + auto bounds = getLocalBounds().toFloat(); +} + +void LicenseRegistrationComponent::resized() +{ + auto bounds = getLocalBounds().reduced(20); + + titleLabel.setBounds(bounds.removeFromTop(40)); + bounds.removeFromTop(20); + + instructionsLabel.setBounds(bounds.removeFromTop(30)); + bounds.removeFromTop(20); + + auto row = bounds.removeFromTop(35); + licenseKeyEditor.setBounds(row.reduced(50, 0)); + bounds.removeFromTop(20); + + verifyButton.setBounds(bounds.removeFromTop(40).withSizeKeepingCentre(120, 40)); +} + +void LicenseRegistrationComponent::verifyLicense(const juce::String& licenseKey, bool showErrorDialog) +{ + if (licenseKey.isEmpty()) + return; + + isVerifying = true; + verifyButton.setEnabled(false); + + juce::URL url("https://api.osci-render.com/api/verify-license"); + + auto jsonObj = std::make_unique(); + jsonObj->setProperty("license_key", licenseKey); + juce::var jsonData(jsonObj.release()); + + url = url.withPOSTData(juce::JSON::toString(jsonData)); + + auto webStream = url.createInputStream(false, nullptr, nullptr, + "Content-Type: application/json", + 10000); + + bool successfullyVerified = false; + + if (webStream != nullptr) + { + auto response = webStream->readEntireStreamAsString(); + DBG(response); + auto json = juce::JSON::parse(response); + + if (json.hasProperty("success")) { + bool success = json["success"]; + + if (success && json.hasProperty("valid") && json.hasProperty("purchase")) { + bool valid = json["valid"]; + + auto purchase = json["purchase"].getDynamicObject(); + auto productId = purchase->getProperty("product_id").toString(); + + if (success && valid && productId == SOSCI_PRODUCT_ID) + { + // Save the license key and validation timestamp + audioProcessor.setGlobalValue("license_key", licenseKey); + audioProcessor.setGlobalValue("license_last_validated", juce::Time::getCurrentTime().toISO8601(true)); + audioProcessor.saveGlobalSettings(); + + successfullyVerified = true; + + setVisible(false); + startTimer(1000 * 60 * 60); // Check every hour + } + else if (showErrorDialog) + { + juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::WarningIcon, + "Invalid License", + "The license key you entered is not valid. Please check and try again."); + } + else + { + // Background check failed, clear the license + clearLicense(); + } + } else if (showErrorDialog && json.hasProperty("message")) { + auto message = json["message"].toString(); + juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::WarningIcon, "Error", message); + } + } + } + else if (showErrorDialog) + { + juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::WarningIcon, + "Connection Error", + "Could not connect to the license server. Please check your internet connection and try again."); + } + + isVerifying = false; + verifyButton.setEnabled(true); + + if (onLicenseVerified != nullptr) { + onLicenseVerified(successfullyVerified); + } +} + +bool LicenseRegistrationComponent::validateSavedLicense() +{ + auto savedKey = audioProcessor.getGlobalStringValue("license_key"); + if (savedKey.isNotEmpty()) + { + auto lastValidated = audioProcessor.getGlobalStringValue("license_last_validated"); + if (lastValidated.isNotEmpty()) + { + // Verify in the background without showing error dialogs + verifyLicense(savedKey, false); + return true; + } + } + return false; +} + +void LicenseRegistrationComponent::clearLicense() +{ + audioProcessor.removeGlobalValue("license_key"); + audioProcessor.removeGlobalValue("license_last_validated"); + audioProcessor.saveGlobalSettings(); + setVisible(true); +} + +void LicenseRegistrationComponent::timerCallback() +{ + auto savedKey = audioProcessor.getGlobalStringValue("license_key"); + if (savedKey.isNotEmpty()) { + verifyLicense(savedKey, false); + } +} diff --git a/Source/components/LicenseRegistrationComponent.h b/Source/components/LicenseRegistrationComponent.h new file mode 100644 index 0000000..7fcc2c4 --- /dev/null +++ b/Source/components/LicenseRegistrationComponent.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include "../CommonPluginProcessor.h" + +class LicenseRegistrationComponent : public juce::Component, + public juce::Timer +{ +public: + LicenseRegistrationComponent(CommonAudioProcessor& processor, std::function onLicenseVerified); + ~LicenseRegistrationComponent() override; + + void paint(juce::Graphics& g) override; + void resized() override; + + // Timer callback for background license verification + void timerCallback() override; + +private: + void verifyLicense(const juce::String& licenseKey, bool showErrorDialog = true); + bool validateSavedLicense(); + void clearLicense(); + void setupComponents(); + + CommonAudioProcessor& audioProcessor; + juce::Label titleLabel; + juce::Label instructionsLabel; + juce::TextEditor licenseKeyEditor; + juce::TextButton verifyButton; + bool isVerifying = false; + + const juce::String SOSCI_PRODUCT_ID = "Hsr9C58_YhTxYP0MNvsIow=="; + + std::function onLicenseVerified; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LicenseRegistrationComponent) +}; diff --git a/osci-render.jucer b/osci-render.jucer index a27b80c..a101269 100644 --- a/osci-render.jucer +++ b/osci-render.jucer @@ -184,6 +184,10 @@ file="Source/components/ErrorCodeEditorComponent.h"/> + + + +