feat: remove premium features (#380)

This commit is contained in:
DecafDev 2025-03-30 08:42:12 -06:00 committed by GitHub
parent 4cb716297c
commit c79f52e900
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 23 additions and 436 deletions

View file

@ -21,7 +21,6 @@ Vault Explorer is a work in progress. Please check the GitHub repository and doc
- [Screenshots](#screenshots)
- [Installation](#installation)
- [Usage](#usage)
- [Premium](#premium)
- [Features](#features)
- [Network use](#network-use)
- [Contributing](#contributing)
@ -79,12 +78,6 @@ Vault Explorer is a work in progress. Please check the GitHub repository and doc
Click the compass button on the left-hand sidebar to open the vault explorer view.
## Premium
Premium features are available to users who purchase a [Vault Explorer license](https://vaultexplorer.com/docs/premium/).
Please do not share your license key with anyone. Shared license keys will be deactivated.
## Features
| Name | Categories | Documented |

View file

@ -14,7 +14,6 @@ export enum PluginEvent {
FEED_CONTENT_SETTING_CHANGE = "feed-content-setting-change",
COVER_IMAGE_SOURCE_SETTING_CHANGE = "cover-image-source-setting-change",
PROPERTY_SETTING_CHANGE = "property-setting-change",
LICENSE_KEY_VALIDATION_CHANGE = "license-key-validation-change",
CLOCK_UPDATES_SETTING_CHANGE = "clock-updates-setting-change",
FILTER_TOGGLE_SETTING_CHANGE = "filter-toggle-setting-change",
SCROLL_BUTTONS_SETTING_CHANGE = "scroll-buttons-setting-change",

View file

@ -16,7 +16,6 @@ import { formatMessageForLogger, stringToLogLevel } from "./logger";
import { preformMigrations } from "./migrations";
import "./styles.css";
import { clearSMICache } from "./svelte/app/services/smi-cache";
import License from "./svelte/shared/services/license";
import store from "./svelte/shared/services/store";
import type { VaultExplorerPluginSettings } from "./types";
import { isVersionLessThan } from "./utils";
@ -31,8 +30,6 @@ export default class VaultExplorerPlugin extends Plugin {
store.plugin.set(this);
await License.getInstance().loadStoredKey();
this.registerView(
VAULT_EXPLORER_VIEW,
(leaf) => new VaultExplorerView(leaf, this)
@ -202,11 +199,6 @@ export default class VaultExplorerPlugin extends Plugin {
//Clean up the old device id from the versioning system
const LOCAL_STORAGE_ID = "vault-explorer-id";
localStorage.removeItem(LOCAL_STORAGE_ID);
//Clean up the old device id from the versioning system
const LOCAL_STORAGE_LICENSE_KEY =
"vault-explorer-license-key";
localStorage.removeItem(LOCAL_STORAGE_LICENSE_KEY);
}
if (isVersionLessThan(loadedVersion, "1.37.1")) {
console.log("Clearing image cache");

View file

@ -1,3 +0,0 @@
.vault-explorer-premium-setting {
color: var(--text-accent);
}

View file

@ -24,21 +24,17 @@ import EventManager from "src/event/event-manager";
import { PluginEvent } from "src/event/types";
import { TExplorerView } from "src/types";
import ImageSourceApp from "../svelte/image-source-app/index.svelte";
import LicenseKeyApp from "../svelte/license-key-app/index.svelte";
import { clearSMICache } from "src/svelte/app/services/smi-cache";
import "./styles.css";
export default class VaultExplorerSettingsTab extends PluginSettingTab {
plugin: VaultExplorerPlugin;
licenseKeyApp: LicenseKeyApp | null;
imageSourceApp: ImageSourceApp | null;
constructor(app: App, plugin: VaultExplorerPlugin) {
super(app, plugin);
this.plugin = plugin;
this.licenseKeyApp = null;
this.imageSourceApp = null;
}
@ -629,12 +625,6 @@ export default class VaultExplorerSettingsTab extends PluginSettingTab {
})
);
new Setting(containerEl).setName("Premium").setHeading();
this.licenseKeyApp = new LicenseKeyApp({
target: containerEl,
});
new Setting(containerEl).setName("Debugging").setHeading();
new Setting(containerEl)
.setName("Log level")
@ -693,7 +683,6 @@ export default class VaultExplorerSettingsTab extends PluginSettingTab {
}
onClose() {
this.licenseKeyApp?.$destroy();
this.imageSourceApp?.$destroy();
}

View file

@ -35,7 +35,6 @@
export let path: string;
export let createdMillis: number;
export let content: string | null;
export let enablePremiumFeatures: boolean;
let ref: HTMLElement | null = null;
let enableFileIcons = false;
@ -147,14 +146,7 @@
function handleCardContextMenu(e: Event) {
const nativeEvent = e as MouseEvent;
const { app, settings } = plugin;
openContextMenu(
nativeEvent,
path,
app,
settings,
enablePremiumFeatures,
{},
);
openContextMenu(nativeEvent, path, app, settings, {});
}
function handleCardMouseOver(e: MouseEvent) {

View file

@ -5,7 +5,6 @@
export let data: FileRenderData[] = [];
export let startIndex;
export let pageLength;
export let enablePremiumFeatures: boolean;
let filteredItems: FileRenderData[] = [];
@ -24,7 +23,6 @@
<div class="vault-explorer-feed-view">
{#each filteredItems as fileRenderData (fileRenderData.id)}
<FeedCard
{enablePremiumFeatures}
displayName={fileRenderData.displayName}
extension={fileRenderData.extension}
baseName={fileRenderData.baseName}

View file

@ -42,7 +42,6 @@
export let custom2: string | null;
export let custom3: string | null;
export let coverImageFit: CoverImageFit;
export let enablePremiumFeatures: boolean;
let plugin: VaultExplorerPlugin;
let enableFileIcons: boolean = false;
@ -110,21 +109,12 @@
const showCoverImageOptions = path.endsWith(".md");
const { app, settings } = plugin;
openContextMenu(
nativeEvent,
path,
app,
settings,
enablePremiumFeatures,
{
coverImageFit: showCoverImageOptions
? coverImageFit
: undefined,
onCoverImageFitChange: showCoverImageOptions
? handleCoverImageFitChange
: undefined,
},
);
openContextMenu(nativeEvent, path, app, settings, {
coverImageFit: showCoverImageOptions ? coverImageFit : undefined,
onCoverImageFitChange: showCoverImageOptions
? handleCoverImageFitChange
: undefined,
});
}
function handleCardMouseOver(e: MouseEvent) {

View file

@ -5,7 +5,6 @@
export let data: FileRenderData[];
export let startIndex: number;
export let pageLength: number;
export let enablePremiumFeatures: boolean;
let filteredItems: FileRenderData[] = [];
@ -25,7 +24,6 @@
<div class="vault-explorer-grid-view__container">
{#each filteredItems as fileRenderData (fileRenderData.id)}
<GridCard
{enablePremiumFeatures}
displayName={fileRenderData.displayName}
path={fileRenderData.path}
coverImageFit={fileRenderData.coverImageFit}

View file

@ -1,17 +1,17 @@
<script lang="ts">
import VaultExplorerPlugin from "src/main";
import store from "../../shared/services/store";
import Tag from "src/svelte/shared/components/tag.svelte";
import Wrap from "src/svelte/shared/components/wrap.svelte";
import Icon from "src/svelte/shared/components/icon.svelte";
import Stack from "src/svelte/shared/components/stack.svelte";
import { getIconIdForFile } from "../services/file-icon";
import { onMount } from "svelte";
import { HOVER_LINK_SOURCE_ID } from "src/constants";
import EventManager from "src/event/event-manager";
import { PluginEvent } from "src/event/types";
import VaultExplorerPlugin from "src/main";
import Icon from "src/svelte/shared/components/icon.svelte";
import Stack from "src/svelte/shared/components/stack.svelte";
import Tag from "src/svelte/shared/components/tag.svelte";
import Wrap from "src/svelte/shared/components/wrap.svelte";
import { onMount } from "svelte";
import store from "../../shared/services/store";
import { openContextMenu } from "../services/context-menu";
import { getIconIdForFile } from "../services/file-icon";
import { openInCurrentTab } from "../services/open-file";
import { HOVER_LINK_SOURCE_ID } from "src/constants";
export let displayName: string;
export let baseName: string;
@ -21,7 +21,6 @@
let showTags: boolean;
export let isSmallScreenSize: boolean;
export let enablePremiumFeatures: boolean;
let enableFileIcons: boolean = false;
let ref: HTMLElement | null = null;
@ -78,14 +77,7 @@
function handleItemContextMenu(e: Event) {
const nativeEvent = e as MouseEvent;
const { app, settings } = plugin;
openContextMenu(
nativeEvent,
path,
app,
settings,
enablePremiumFeatures,
{},
);
openContextMenu(nativeEvent, path, app, settings, {});
}
function handleItemMouseOver(e: MouseEvent) {

View file

@ -7,7 +7,6 @@
export let startIndex: number;
export let pageLength: number;
export let isSmallScreenSize: boolean;
export let enablePremiumFeatures: boolean;
let filteredItems: FileRenderData[] = [];
@ -26,7 +25,6 @@
<div class="vault-explorer-list-view">
{#each filteredItems as fileRenderData (fileRenderData.id)}
<ListItem
{enablePremiumFeatures}
displayName={fileRenderData.displayName}
extension={fileRenderData.extension}
baseName={fileRenderData.baseName}

View file

@ -24,7 +24,6 @@
export let data: FileRenderData[];
export let startIndex: number;
export let pageLength: number;
export let enablePremiumFeatures: boolean;
let filteredItems: FileRenderData[] = [];
let plugin: VaultExplorerPlugin | null = null;
@ -100,14 +99,7 @@
const nativeEvent = e as MouseEvent;
const { app, settings } = plugin;
openContextMenu(
nativeEvent,
path,
app,
settings,
enablePremiumFeatures,
{},
);
openContextMenu(nativeEvent, path, app, settings, {});
}
function getValue(item: FileRenderData, column: TColumn): unknown {

View file

@ -20,7 +20,7 @@
TTableView,
} from "src/types";
import { TExplorerView, } from "src/types";
import { TExplorerView } from "src/types";
import { onMount } from "svelte";
import Flex from "../shared/components/flex.svelte";
import IconButton from "../shared/components/icon-button.svelte";
@ -29,7 +29,6 @@
import TabList from "../shared/components/tab-list.svelte";
import Tab from "../shared/components/tab.svelte";
import Wrap from "../shared/components/wrap.svelte";
import License from "../shared/services/license";
import store from "../shared/services/store";
import {
getStartOfLastWeekMillis,
@ -109,7 +108,6 @@
let randomSortCache: RandomFileSortCache = new Map();
let isSmallScreenSize: boolean = false;
let enablePremiumFeatures: boolean = false;
//Views
let gridView: TGridView = {
@ -151,12 +149,6 @@
initialRender = false;
});
License.getInstance()
.getHasValidKeyStore()
.subscribe((hasValidKey) => {
enablePremiumFeatures = hasValidKey;
});
randomFileSortStore.subscribe((value) => {
randomSortCache = value;
});
@ -1046,7 +1038,6 @@
data={renderData}
{startIndex}
{pageLength}
{enablePremiumFeatures}
on:coverImageFitChange={handleCoverImageFitChange}
/>
{:else if currentView === "list"}
@ -1055,22 +1046,11 @@
{isSmallScreenSize}
{startIndex}
{pageLength}
{enablePremiumFeatures}
/>
{:else if currentView === "table"}
<TableView
data={renderData}
{startIndex}
{pageLength}
{enablePremiumFeatures}
/>
<TableView data={renderData} {startIndex} {pageLength} />
{:else if currentView === "feed"}
<FeedView
data={renderData}
{startIndex}
{pageLength}
{enablePremiumFeatures}
/>
<FeedView data={renderData} {startIndex} {pageLength} />
{/if}
<PaginationIndicator
{startIndex}

View file

@ -1,4 +1,4 @@
import { App, Menu, Notice } from "obsidian";
import { App, Menu } from "obsidian";
import type { CoverImageFit, VaultExplorerPluginSettings } from "src/types";
export const openContextMenu = (
@ -6,7 +6,6 @@ export const openContextMenu = (
filePath: string,
app: App,
settings: VaultExplorerPluginSettings,
enablePremiumFeatures: boolean,
{
coverImageFit,
onCoverImageFitChange,
@ -51,12 +50,6 @@ export const openContextMenu = (
menu.addItem((item) => {
item.setTitle("Delete");
item.onClick(async () => {
if (!enablePremiumFeatures) {
new Notice(
"This feature requires a premium Vault Explorer license."
);
return;
}
if (confirmBeforeDelete) {
if (confirm("Are you sure you want to delete this file?")) {
await deleteFile(app, filePath);

View file

@ -15,20 +15,9 @@
export let condition: FilterCondition;
export let isEnabled: boolean;
import PremiumLink from "src/svelte/shared/components/premium-link.svelte";
import PremiumMessage from "src/svelte/shared/components/premium-message.svelte";
import License from "src/svelte/shared/services/license";
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
let hasValidLicenseKey = false;
License.getInstance()
.getHasValidKeyStore()
.subscribe((hasValidKey) => {
hasValidLicenseKey = hasValidKey;
});
function handleValueChange(e: Event) {
const value = (e.target as HTMLInputElement).value;
dispatch("ruleValueChange", { id, value });
@ -54,21 +43,12 @@
{#if condition !== ContentFilterCondition.IS_EMPTY && condition !== ContentFilterCondition.IS_NOT_EMPTY}
<input
type="text"
disabled={!hasValidLicenseKey}
placeholder="value"
{value}
on:input={handleValueChange}
/>
{/if}
</svelte:fragment>
<svelte:fragment slot="after-toggle">
{#if type === FilterRuleType.CONTENT && !hasValidLicenseKey}
<div>
<PremiumMessage />
<PremiumLink />
</div>
{/if}
</svelte:fragment>
</FilterRule>
<style>

View file

@ -4,7 +4,6 @@
import Stack from "src/svelte/shared/components/stack.svelte";
import Switch from "src/svelte/shared/components/switch.svelte";
import Wrap from "src/svelte/shared/components/wrap.svelte";
import License from "src/svelte/shared/services/license";
import {
CheckboxFilterCondition,
ContentFilterCondition,
@ -33,16 +32,8 @@
export let condition: FilterCondition;
export let isEnabled: boolean;
let hasValidLicenseKey = false;
const dispatch = createEventDispatcher();
License.getInstance()
.getHasValidKeyStore()
.subscribe((hasValidKey) => {
hasValidLicenseKey = hasValidKey;
});
function handleActionsClick(e: CustomEvent) {
const nativeEvent = e.detail.nativeEvent as MouseEvent;
@ -141,11 +132,7 @@
{/each}
</select>
<slot name="before-condition"></slot>
<select
disabled={type === FilterRuleType.CONTENT && !hasValidLicenseKey}
value={condition}
on:change={handleConditionChange}
>
<select value={condition} on:change={handleConditionChange}>
{#each filterConditions as condition}
<option value={condition}>
{getDisplayNameForFilterCondition(condition)}

View file

@ -1,129 +0,0 @@
<script lang="ts">
import { onMount } from "svelte";
import License from "../shared/services/license";
import EventManager from "src/event/event-manager";
import PremiumLink from "../shared/components/premium-link.svelte";
import { PluginEvent } from "src/event/types";
interface Message {
type: "success" | "failure" | "info";
text: string;
}
let hasValidLicenseKey = false;
let message: Message | null = null;
const LICENSE_KEY_SIZE = 148;
onMount(() => {
const hasValidKey = License.getInstance().getHasValidKey();
if (hasValidKey) {
message = {
type: "success",
text: "Premium features are enabled.",
};
}
hasValidLicenseKey = hasValidKey;
});
async function handleInputChange(e: Event) {
const value = (e.target as HTMLInputElement).value;
if (value.length < LICENSE_KEY_SIZE) return;
message = {
type: "info",
text: "Validating key...",
};
const result = await License.getInstance().addKey(value);
if (result) {
hasValidLicenseKey = true;
message = {
type: "success",
text: "Premium features are enabled.",
};
EventManager.getInstance().emit(
PluginEvent.LICENSE_KEY_VALIDATION_CHANGE,
true,
);
} else {
hasValidLicenseKey = false;
message = {
type: "failure",
text: "Invalid key.",
};
}
}
function handleRemoveButtonClick() {
License.getInstance().removeKey();
hasValidLicenseKey = false;
message = null;
EventManager.getInstance().emit(
PluginEvent.LICENSE_KEY_VALIDATION_CHANGE,
false,
);
}
function getMessageClassName(message: Message | null) {
let className = "vault-explorer-setting-message";
if (message !== null) {
const { type } = message;
if (type === "success") {
className += " vault-explorer-setting-message--success";
} else if (type === "failure") {
className += " vault-explorer-setting-message--failure";
}
}
return className;
}
$: messageClassName = getMessageClassName(message);
</script>
<div class="setting-item">
<div class="setting-item-info">
<div class="setting-item-name">License key</div>
<div class="setting-item-description">
<div>Enter your license key to enable premium features.</div>
{#if message == null}
<PremiumLink />
{/if}
{#if message != null}
<div class={messageClassName}>
{message?.text ?? ""}
</div>
{/if}
</div>
</div>
<div class="setting-item-control">
{#if hasValidLicenseKey === false}
<input
type="text"
maxlength={LICENSE_KEY_SIZE}
on:input={handleInputChange}
/>
{/if}
{#if hasValidLicenseKey === true}
<button class="mod-destructive" on:click={handleRemoveButtonClick}
>Remove key</button
>
{/if}
</div>
</div>
<style>
.vault-explorer-setting-message {
color: var(--text-muted);
}
.vault-explorer-setting-message--success {
color: var(--color-green);
}
.vault-explorer-setting-message--failure {
color: var(--color-red);
}
</style>

View file

@ -1,3 +0,0 @@
<a href="https://vaultexplorer.com/docs/premium/" target="_blank" rel="noopener"
>Learn more</a
>

View file

@ -1,9 +0,0 @@
<div class="vault-explorer-premium-message">
This feature requires a premium license
</div>
<style>
.vault-explorer-premium-message {
font-size: var(--font-smallest);
}
</style>

View file

@ -1,142 +0,0 @@
import Logger from "js-logger";
import { writable } from "svelte/store";
import crypto from "crypto";
const LOCAL_STORAGE_LICENSE_KEY = "vault-explorer-license";
const PUBLIC_KEY_PEM = `
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAO539qAsgBzbukUNDuOtPZKXNj8MSXvt3zS1ci4plDBA=
-----END PUBLIC KEY-----
`;
export default class License {
private licenseKey: string;
private hasValidKey: boolean;
private hasValidKeyStore = writable<boolean>();
private static instance: License;
constructor() {
this.licenseKey = "";
this.hasValidKey = false;
this.hasValidKeyStore.set(false);
}
async loadStoredKey() {
const storedKey = this.getStoredLicenseKey();
if (storedKey) {
const isValid = await this.validateKey(storedKey);
this.licenseKey = storedKey;
this.hasValidKey = isValid;
this.hasValidKeyStore.set(isValid);
}
}
async addKey(licenseKey: string) {
Logger.trace({
fileName: "license.ts",
functionName: "addKey",
message: "called",
});
const result = await this.validateKey(licenseKey);
if (result) {
this.setStoredKey(licenseKey);
this.hasValidKeyStore.set(true);
this.hasValidKey = true;
}
return result;
}
/**
* Verify the licenseKey using the public key.
* @param signature- The licenseKey to verify. This is created by signing a file with the private key.
*/
async validateKey(licenseKey: string) {
Logger.trace({
fileName: "license.ts",
functionName: "validateKey",
message: "called",
});
try {
// Decode Base64 to buffer
const decodedBuffer = Buffer.from(licenseKey, "base64");
const decodedString = decodedBuffer.toString("utf-8");
const split = decodedString.split("|");
const signatureBase64 = split[0];
const data = split[1].trim();
const signatureBuffer = Buffer.from(signatureBase64, "base64");
const dataBuffer = Buffer.from(data);
const verify = crypto.createVerify("SHA256");
verify.update(data);
verify.end();
return crypto.verify(
null,
dataBuffer,
{
key: PUBLIC_KEY_PEM,
format: "pem",
type: "spki",
},
signatureBuffer
);
} catch (err) {
return false;
}
}
removeKey() {
Logger.trace({
fileName: "license.ts",
functionName: "removeKey",
message: "called",
});
this.setStoredKey(null);
this.hasValidKeyStore.set(false);
this.hasValidKey = false;
}
private setStoredKey(value: string | null) {
Logger.trace({
fileName: "license.ts",
functionName: "setStoredKey",
message: "called",
});
if (value !== null) {
localStorage.setItem(LOCAL_STORAGE_LICENSE_KEY, value);
} else {
localStorage.removeItem(LOCAL_STORAGE_LICENSE_KEY);
}
}
private getStoredLicenseKey() {
return localStorage.getItem(LOCAL_STORAGE_LICENSE_KEY);
}
getHasValidKey() {
return this.hasValidKey;
}
getHasValidKeyStore() {
return this.hasValidKeyStore;
}
getLicenseKey() {
return this.licenseKey;
}
static getInstance() {
if (!this.instance) {
this.instance = new License();
}
return this.instance;
}
}