Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[x-license] Add support for plan version #13459

Merged
merged 25 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
190a18e
license update proposal
michelengelen Jun 12, 2024
1706016
Revert "license update proposal"
michelengelen Jun 13, 2024
93a4e9a
second iteration on license update
michelengelen Jun 13, 2024
8b4369e
3rd iteration
michelengelen Jun 13, 2024
f815941
changed status return on charts/treeview check
michelengelen Jun 13, 2024
3ccba21
removed tests for now
michelengelen Jun 13, 2024
46cf0d5
adjusted things and fixed tests
michelengelen Jun 13, 2024
da18b3f
review remarks
michelengelen Jun 14, 2024
ac5e804
removed productScope optional marking
michelengelen Jun 14, 2024
300e0b5
Update verifyLicense.ts
michelengelen Jun 14, 2024
e62b985
added license generation tests
michelengelen Jun 17, 2024
f4b0ed5
fixed typo
michelengelen Jun 17, 2024
b5cec8b
Added 'Mui X:' prefix on error
michelengelen Jun 17, 2024
88056dd
Added Key Version 2.2 tests
michelengelen Jun 18, 2024
508c785
added product scope tests
michelengelen Jun 18, 2024
c6e7a17
added useLicenseVerifier test
michelengelen Jun 18, 2024
1a26ce8
Apply suggestions from code review
michelengelen Jun 19, 2024
e572ab4
added some tests requested from @cherniavskii
michelengelen Jun 19, 2024
c67d0f9
Update packages/x-license/src/utils/licenseErrorMessageUtils.ts
michelengelen Jun 19, 2024
001a411
changed error wording
michelengelen Jun 19, 2024
4fffe6a
added link to license page back in
michelengelen Jun 20, 2024
c04fa6b
Merge remote-tracking branch 'refs/remotes/upstream/master' into lice…
michelengelen Jun 20, 2024
f19b30e
added text for new license state to watermark
michelengelen Jun 20, 2024
a0da60e
fix tests
michelengelen Jun 20, 2024
a19da9d
fix tests
michelengelen Jun 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 3 additions & 19 deletions packages/x-license/src/generateLicense/generateLicense.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { LICENSE_UPDATE_TIMESTAMPS } from '@mui/x-license/generateLicense/licenseUpdateTimestamps';
import { md5 } from '../encoding/md5';
import { base64Encode } from '../encoding/base64';
import { LICENSE_SCOPES, LicenseScope } from '../utils/licenseScope';
Expand All @@ -9,7 +8,6 @@ const licenseVersion = '2';
export interface LicenseDetails {
orderNumber: string;
expiryDate: Date;
purchaseDate?: Date;
scope: LicenseScope;
licensingModel: LicensingModel;
}
Expand All @@ -23,23 +21,9 @@ function getClearLicenseString(details: LicenseDetails) {
throw new Error('MUI X: Invalid licensing model');
}

if (!details.purchaseDate || new Date().getTime() < LICENSE_UPDATE_TIMESTAMPS['2024-07']) {
throw new Error('MUI X: Licenses generated without a purchaseDate are not supported');
}

const parts = [
`O=${details.orderNumber}`,
`E=${details.expiryDate.getTime()}`,
`S=${details.scope}`,
`LM=${details.licensingModel}`,
`KV=${licenseVersion}`,
];

if (details.purchaseDate) {
parts.splice(1, 0, `P=${details.purchaseDate.getTime()}`);
}

return parts.join(',');
return `O=${details.orderNumber},E=${details.expiryDate.getTime()},S=${details.scope},LM=${
details.licensingModel
},KV=${licenseVersion}`;
}

export function generateLicense(details: LicenseDetails) {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
showExpiredPackageVersionError,
} from '../utils/licenseErrorMessageUtils';
import { LICENSE_STATUS, LicenseStatus } from '../utils/licenseStatus';
import { LicenseScope } from '../utils/licenseScope';
import { extractAcceptedScopes, LicenseScope } from '../utils/licenseScope';
import MuiLicenseInfoContext from '../Unstable_LicenseInfoProvider/MuiLicenseInfoContext';

export type MuiCommercialPackageName =
Expand Down Expand Up @@ -47,16 +47,13 @@ export function useLicenseVerifier(
return sharedLicenseStatuses[packageName]!.licenseVerifier;
}

const acceptedScopes: LicenseScope[] = packageName.includes('premium')
? ['premium']
: ['pro', 'premium'];
const acceptedScopes: readonly LicenseScope[] = extractAcceptedScopes(packageName);
michelengelen marked this conversation as resolved.
Show resolved Hide resolved

const plan = packageName.includes('premium') ? 'Premium' : 'Pro';
const licenseStatus = verifyLicense({
releaseInfo,
licenseKey,
acceptedScopes,
packageName,
});

const fullPackageName = `@mui/${packageName}`;
Expand Down
23 changes: 21 additions & 2 deletions packages/x-license/src/utils/licenseScope.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
export const LICENSE_SCOPES = ['pro', 'premium'] as const;
export const LICENSE_SCOPES = ['pro', 'premium', 'pro2024', 'premium2024'] as const;
michelengelen marked this conversation as resolved.
Show resolved Hide resolved
export const PRODUCT_SCOPES = ['data-grid', 'date-pickers', 'charts', 'tree-view'] as const;

export type LicenseScope = (typeof LICENSE_SCOPES)[number];
export type ProductScope = (typeof PRODUCT_SCOPES)[number];
export type ProductScope = (typeof PRODUCT_SCOPES)[number] | null;

export const extractProductScope = (packageName: string): ProductScope | null => {
const regex = /x-(.*?)(-pro|-premium)?$/;
const match = packageName.match(regex);
return match ? (match[1] as ProductScope) : null;
};

export const extractAcceptedScopes = (packageName: string): readonly LicenseScope[] => {
const productScope = extractProductScope(packageName);

const scopes = packageName.includes('premium')
? LICENSE_SCOPES.filter((scope) => scope.includes('premium'))
: LICENSE_SCOPES;

if (productScope === 'charts' || productScope === 'tree-view') {
return scopes.filter((scope) => scope.includes('2024'));
}
return scopes;
};
1 change: 0 additions & 1 deletion packages/x-license/src/utils/licenseStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export enum LICENSE_STATUS {
ExpiredVersion = 'ExpiredVersion',
Valid = 'Valid',
OutOfScope = 'OutOfScope',
ProductScope = 'ProductScope',
}

export type LicenseStatus = keyof typeof LICENSE_STATUS;
52 changes: 3 additions & 49 deletions packages/x-license/src/verifyLicense/verifyLicense.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { LICENSE_UPDATE_TIMESTAMPS } from '@mui/x-license/generateLicense/licenseUpdateTimestamps';
import { base64Decode, base64Encode } from '../encoding/base64';
import { md5 } from '../encoding/md5';
import { LICENSE_STATUS, LicenseStatus } from '../utils/licenseStatus';
import { LicenseScope, LICENSE_SCOPES, ProductScope } from '../utils/licenseScope';
import { LicenseScope, LICENSE_SCOPES } from '../utils/licenseScope';
import { LicensingModel, LICENSING_MODELS } from '../utils/licensingModel';

const getDefaultReleaseDate = () => {
Expand All @@ -22,7 +21,6 @@ interface MuiLicense {
licensingModel: LicensingModel | null;
scope: LicenseScope | null;
expiryTimestamp: number | null;
purchaseTimestamp?: number | null;
}

/**
Expand All @@ -47,8 +45,7 @@ const decodeLicenseVersion1 = (license: string): MuiLicense => {
};

/**
* Format: O=${orderNumber}[,P=${purchaseTimestamp}],E=${expiryTimestamp},S=${scope},LM=${licensingModel},KV=2`;
* purchaseTimestamp is optional.
* Format: O=${orderNumber},E=${expiryTimestamp},S=${scope},LM=${licensingModel},KV=2`;
*/
const decodeLicenseVersion2 = (license: string): MuiLicense => {
const licenseInfo: MuiLicense = {
Expand Down Expand Up @@ -76,13 +73,6 @@ const decodeLicenseVersion2 = (license: string): MuiLicense => {
licenseInfo.expiryTimestamp = expiryTimestamp;
}
}

if (key === 'P') {
const purchaseTimestamp = parseInt(value, 10);
if (purchaseTimestamp && !Number.isNaN(purchaseTimestamp)) {
licenseInfo.purchaseTimestamp = purchaseTimestamp;
}
}
});

return licenseInfo;
Expand All @@ -105,22 +95,14 @@ const decodeLicense = (encodedLicense: string): MuiLicense | null => {
return null;
};

const extractProductScope = (packageName: string): ProductScope | null => {
const regex = /x-(.*?)(-pro|-premium)?$/;
const match = packageName.match(regex);
return match ? (match[1] as ProductScope) : null;
};

export function verifyLicense({
releaseInfo,
licenseKey,
acceptedScopes,
packageName,
}: {
releaseInfo: string;
licenseKey: string | undefined;
acceptedScopes: LicenseScope[];
packageName: string;
acceptedScopes: readonly LicenseScope[];
}): { status: LicenseStatus; meta?: any } {
if (!releaseInfo) {
throw new Error('MUI X: The release information is missing. Not able to validate license.');
Expand Down Expand Up @@ -154,11 +136,6 @@ export function verifyLicense({
return { status: LICENSE_STATUS.Invalid };
}

if (license.purchaseTimestamp && license.purchaseTimestamp > license.expiryTimestamp) {
console.error('MUI X: Error checking license. Purchase timestamp cannot be later than expiry!');
return { status: LICENSE_STATUS.Invalid };
}

if (license.licensingModel === 'perpetual' || process.env.NODE_ENV === 'production') {
const pkgTimestamp = parseInt(base64Decode(releaseInfo), 10);
if (Number.isNaN(pkgTimestamp)) {
Expand Down Expand Up @@ -196,28 +173,5 @@ export function verifyLicense({
return { status: LICENSE_STATUS.OutOfScope };
}

if (packageName) {
const productScope = extractProductScope(packageName);

switch (productScope) {
case 'charts':
case 'tree-view':
if (
license.purchaseTimestamp &&
license.purchaseTimestamp >= LICENSE_UPDATE_TIMESTAMPS['2024-06']
) {
// NEW LICENSE
// therefore usage of charts-pro and tree-view-pro is allowed
break;
}

return { status: LICENSE_STATUS.ProductScope };
case 'data-grid':
case 'date-pickers':
default:
break;
}
}

return { status: LICENSE_STATUS.Valid };
}