strapi-plugin-magic-mark 3.1.0 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/server/index.js +564 -496
- package/dist/server/index.mjs +564 -496
- package/dist/server/src/config/index.d.ts +3 -1
- package/dist/server/src/index.d.ts +3 -1
- package/dist/server/src/utils/logger.d.ts +15 -0
- package/package.json +1 -1
package/dist/server/index.mjs
CHANGED
|
@@ -2,6 +2,65 @@ import crypto from "crypto";
|
|
|
2
2
|
import os from "os";
|
|
3
3
|
import { readFileSync } from "fs";
|
|
4
4
|
import { join } from "path";
|
|
5
|
+
const PLUGIN_NAME = "magic-mark";
|
|
6
|
+
const PREFIX = "[Magic-Mark]";
|
|
7
|
+
function formatMessage(prefix, args) {
|
|
8
|
+
if (args.length === 0) return prefix;
|
|
9
|
+
const parts = args.map(
|
|
10
|
+
(arg) => typeof arg === "string" ? arg : JSON.stringify(arg)
|
|
11
|
+
);
|
|
12
|
+
return `${prefix} ${parts.join(" ")}`;
|
|
13
|
+
}
|
|
14
|
+
function createLogger(strapi) {
|
|
15
|
+
const getDebugMode = () => {
|
|
16
|
+
try {
|
|
17
|
+
const config2 = strapi.config.get(`plugin::${PLUGIN_NAME}`) || {};
|
|
18
|
+
return config2.debug === true;
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
return {
|
|
24
|
+
/**
|
|
25
|
+
* Log info - only when debug: true
|
|
26
|
+
*/
|
|
27
|
+
info: (...args) => {
|
|
28
|
+
if (getDebugMode()) {
|
|
29
|
+
strapi.log.info(formatMessage(PREFIX, args));
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
/**
|
|
33
|
+
* Log debug - only when debug: true
|
|
34
|
+
*/
|
|
35
|
+
debug: (...args) => {
|
|
36
|
+
if (getDebugMode()) {
|
|
37
|
+
strapi.log.debug(formatMessage(PREFIX, args));
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
/**
|
|
41
|
+
* Log warning - only when debug: true
|
|
42
|
+
*/
|
|
43
|
+
warn: (...args) => {
|
|
44
|
+
if (getDebugMode()) {
|
|
45
|
+
strapi.log.warn(formatMessage(PREFIX, args));
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
/**
|
|
49
|
+
* Log error - only when debug: true
|
|
50
|
+
*/
|
|
51
|
+
error: (...args) => {
|
|
52
|
+
if (getDebugMode()) {
|
|
53
|
+
strapi.log.error(formatMessage(PREFIX, args));
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
/**
|
|
57
|
+
* Force log - always logged (for critical errors only)
|
|
58
|
+
*/
|
|
59
|
+
forceError: (...args) => {
|
|
60
|
+
strapi.log.error(formatMessage(PREFIX, args));
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
5
64
|
const magicMarkActions = {
|
|
6
65
|
actions: [
|
|
7
66
|
{
|
|
@@ -21,68 +80,74 @@ const magicMarkActions = {
|
|
|
21
80
|
]
|
|
22
81
|
};
|
|
23
82
|
const register = async ({ strapi }) => {
|
|
83
|
+
const log = createLogger(strapi);
|
|
24
84
|
await strapi.admin.services.permission.actionProvider.registerMany(
|
|
25
85
|
magicMarkActions.actions
|
|
26
86
|
);
|
|
27
|
-
|
|
87
|
+
log.info("Plugin registered successfully");
|
|
28
88
|
};
|
|
29
89
|
const bootstrap = ({ strapi }) => {
|
|
30
|
-
|
|
90
|
+
const log = createLogger(strapi);
|
|
91
|
+
log.info("Plugin bootstrapping...");
|
|
31
92
|
try {
|
|
32
93
|
const licenseGuardService2 = strapi.plugin("magic-mark").service("license-guard");
|
|
33
94
|
setTimeout(async () => {
|
|
34
95
|
const licenseStatus = await licenseGuardService2.initialize();
|
|
35
96
|
if (!licenseStatus.valid) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
97
|
+
log.error("╔════════════════════════════════════════════════════════════════╗");
|
|
98
|
+
log.error("║ [ERROR] MAGICMARK PLUGIN - NO VALID LICENSE ║");
|
|
99
|
+
log.error("║ ║");
|
|
100
|
+
log.error("║ This plugin requires a valid license to operate. ║");
|
|
101
|
+
log.error("║ Please activate your license via Admin UI: ║");
|
|
102
|
+
log.error("║ Go to Settings → MagicMark → License ║");
|
|
103
|
+
log.error("║ ║");
|
|
104
|
+
log.error("║ The plugin will run with limited functionality until ║");
|
|
105
|
+
log.error("║ a valid license is activated. ║");
|
|
106
|
+
log.error("╚════════════════════════════════════════════════════════════════╝");
|
|
46
107
|
} else if (licenseStatus.valid) {
|
|
47
108
|
const pluginStore = strapi.store({
|
|
48
109
|
type: "plugin",
|
|
49
110
|
name: "magic-mark"
|
|
50
111
|
});
|
|
51
112
|
const storedKey = await pluginStore.get({ key: "licenseKey" });
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
113
|
+
log.info("╔════════════════════════════════════════════════════════════════╗");
|
|
114
|
+
log.info("║ [SUCCESS] MAGICMARK PLUGIN LICENSE ACTIVE ║");
|
|
115
|
+
log.info("║ ║");
|
|
55
116
|
if (licenseStatus.data) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
117
|
+
log.info(`║ License: ${licenseStatus.data.licenseKey} ║`);
|
|
118
|
+
log.info(`║ User: ${licenseStatus.data.firstName} ${licenseStatus.data.lastName}`.padEnd(66) + "║");
|
|
119
|
+
log.info(`║ Email: ${licenseStatus.data.email}`.padEnd(66) + "║");
|
|
59
120
|
} else if (storedKey) {
|
|
60
|
-
|
|
61
|
-
|
|
121
|
+
log.info(`║ License: ${storedKey} (Offline Mode) ║`);
|
|
122
|
+
log.info(`║ Status: Grace Period Active ║`);
|
|
62
123
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
124
|
+
log.info("║ ║");
|
|
125
|
+
log.info("║ [PING] Auto-pinging every 15 minutes ║");
|
|
126
|
+
log.info("╚════════════════════════════════════════════════════════════════╝");
|
|
66
127
|
}
|
|
67
128
|
}, 3e3);
|
|
68
129
|
} catch (error) {
|
|
69
|
-
|
|
130
|
+
log.error("[ERROR] Error initializing License Guard:", error);
|
|
70
131
|
}
|
|
71
|
-
|
|
132
|
+
log.info("Plugin bootstrapped successfully");
|
|
72
133
|
};
|
|
73
134
|
const destroy = ({ strapi }) => {
|
|
135
|
+
const log = createLogger(strapi);
|
|
74
136
|
try {
|
|
75
137
|
const licenseGuardService2 = strapi.plugin("magic-mark")?.service("license-guard");
|
|
76
138
|
if (licenseGuardService2) {
|
|
77
139
|
licenseGuardService2.cleanup();
|
|
78
|
-
|
|
140
|
+
log.info("[SUCCESS] License Guard cleanup completed");
|
|
79
141
|
}
|
|
80
142
|
} catch (error) {
|
|
81
|
-
|
|
143
|
+
log.error("[ERROR] Error during License Guard cleanup:", error);
|
|
82
144
|
}
|
|
83
145
|
};
|
|
84
146
|
const config = {
|
|
85
|
-
default: {
|
|
147
|
+
default: {
|
|
148
|
+
// Enable debug logging (set to true to see all plugin logs)
|
|
149
|
+
debug: false
|
|
150
|
+
},
|
|
86
151
|
validator: () => {
|
|
87
152
|
}
|
|
88
153
|
};
|
|
@@ -1069,507 +1134,510 @@ try {
|
|
|
1069
1134
|
} catch (e) {
|
|
1070
1135
|
}
|
|
1071
1136
|
const LICENSE_SERVER_URL = "https://magicapi.fitlex.me";
|
|
1072
|
-
const licenseGuardService = ({ strapi }) =>
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1137
|
+
const licenseGuardService = ({ strapi }) => {
|
|
1138
|
+
const log = createLogger(strapi);
|
|
1139
|
+
return {
|
|
1140
|
+
/**
|
|
1141
|
+
* Get license server URL (hardcoded and immutable for security)
|
|
1142
|
+
* @returns The fixed license server URL - cannot be overridden
|
|
1143
|
+
*/
|
|
1144
|
+
getLicenseServerUrl() {
|
|
1145
|
+
return LICENSE_SERVER_URL;
|
|
1146
|
+
},
|
|
1147
|
+
/**
|
|
1148
|
+
* Generate a unique device ID based on machine identifiers
|
|
1149
|
+
*/
|
|
1150
|
+
generateDeviceId() {
|
|
1151
|
+
try {
|
|
1152
|
+
const networkInterfaces = os.networkInterfaces();
|
|
1153
|
+
const macAddresses = [];
|
|
1154
|
+
Object.values(networkInterfaces).forEach((interfaces) => {
|
|
1155
|
+
interfaces?.forEach((iface) => {
|
|
1156
|
+
if (iface.mac && iface.mac !== "00:00:00:00:00:00") {
|
|
1157
|
+
macAddresses.push(iface.mac);
|
|
1158
|
+
}
|
|
1159
|
+
});
|
|
1092
1160
|
});
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1161
|
+
const identifier = `${macAddresses.join("-")}-${os.hostname()}`;
|
|
1162
|
+
return crypto.createHash("sha256").update(identifier).digest("hex").substring(0, 32);
|
|
1163
|
+
} catch (error) {
|
|
1164
|
+
log.error("Error generating device ID:", error);
|
|
1165
|
+
return crypto.randomBytes(16).toString("hex");
|
|
1166
|
+
}
|
|
1167
|
+
},
|
|
1168
|
+
/**
|
|
1169
|
+
* Get device name
|
|
1170
|
+
*/
|
|
1171
|
+
getDeviceName() {
|
|
1172
|
+
try {
|
|
1173
|
+
return os.hostname() || "Unknown Device";
|
|
1174
|
+
} catch (error) {
|
|
1175
|
+
return "Unknown Device";
|
|
1176
|
+
}
|
|
1177
|
+
},
|
|
1178
|
+
/**
|
|
1179
|
+
* Get server IP address
|
|
1180
|
+
*/
|
|
1181
|
+
getIpAddress() {
|
|
1182
|
+
try {
|
|
1183
|
+
const networkInterfaces = os.networkInterfaces();
|
|
1184
|
+
for (const name of Object.keys(networkInterfaces)) {
|
|
1185
|
+
const interfaces = networkInterfaces[name];
|
|
1186
|
+
if (interfaces) {
|
|
1187
|
+
for (const iface of interfaces) {
|
|
1188
|
+
if (iface.family === "IPv4" && !iface.internal) {
|
|
1189
|
+
return iface.address;
|
|
1190
|
+
}
|
|
1123
1191
|
}
|
|
1124
1192
|
}
|
|
1125
1193
|
}
|
|
1194
|
+
return "127.0.0.1";
|
|
1195
|
+
} catch (error) {
|
|
1196
|
+
return "127.0.0.1";
|
|
1126
1197
|
}
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1198
|
+
},
|
|
1199
|
+
/**
|
|
1200
|
+
* Get user agent (server context)
|
|
1201
|
+
*/
|
|
1202
|
+
getUserAgent() {
|
|
1203
|
+
const strapiVersion = strapi.config.get("info.strapi") || "5.0.0";
|
|
1204
|
+
return `MagicMark/${pluginVersion} Strapi/${strapiVersion} Node/${process.version} ${os.platform()}/${os.release()}`;
|
|
1205
|
+
},
|
|
1206
|
+
/**
|
|
1207
|
+
* Create a license
|
|
1208
|
+
*/
|
|
1209
|
+
async createLicense({ email, firstName, lastName }) {
|
|
1210
|
+
try {
|
|
1211
|
+
const deviceId = this.generateDeviceId();
|
|
1212
|
+
const deviceName = this.getDeviceName();
|
|
1213
|
+
const ipAddress = this.getIpAddress();
|
|
1214
|
+
const userAgent = this.getUserAgent();
|
|
1215
|
+
const licenseServerUrl = this.getLicenseServerUrl();
|
|
1216
|
+
const response = await fetch(`${licenseServerUrl}/api/licenses/create`, {
|
|
1217
|
+
method: "POST",
|
|
1218
|
+
headers: {
|
|
1219
|
+
"Content-Type": "application/json"
|
|
1220
|
+
},
|
|
1221
|
+
body: JSON.stringify({
|
|
1222
|
+
email,
|
|
1223
|
+
firstName,
|
|
1224
|
+
lastName,
|
|
1225
|
+
deviceName,
|
|
1226
|
+
deviceId,
|
|
1227
|
+
ipAddress,
|
|
1228
|
+
userAgent,
|
|
1229
|
+
pluginName: "magic-mark",
|
|
1230
|
+
productName: "MagicMark - Advanced Query Builder"
|
|
1231
|
+
})
|
|
1232
|
+
});
|
|
1233
|
+
const data = await response.json();
|
|
1234
|
+
if (data.success) {
|
|
1235
|
+
log.info("[SUCCESS] License created successfully:", data.data.licenseKey);
|
|
1236
|
+
return data.data;
|
|
1237
|
+
} else {
|
|
1238
|
+
log.error("[ERROR] License creation failed:", data);
|
|
1239
|
+
return null;
|
|
1240
|
+
}
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
log.error("[ERROR] Error creating license:", error);
|
|
1172
1243
|
return null;
|
|
1173
1244
|
}
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1245
|
+
},
|
|
1246
|
+
/**
|
|
1247
|
+
* Verify a license (with grace period support)
|
|
1248
|
+
*/
|
|
1249
|
+
async verifyLicense(licenseKey, allowGracePeriod = false) {
|
|
1250
|
+
try {
|
|
1251
|
+
const controller = new AbortController();
|
|
1252
|
+
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
1253
|
+
const licenseServerUrl = this.getLicenseServerUrl();
|
|
1254
|
+
const response = await fetch(`${licenseServerUrl}/api/licenses/verify`, {
|
|
1255
|
+
method: "POST",
|
|
1256
|
+
headers: {
|
|
1257
|
+
"Content-Type": "application/json"
|
|
1258
|
+
},
|
|
1259
|
+
body: JSON.stringify({
|
|
1260
|
+
licenseKey,
|
|
1261
|
+
pluginName: "magic-mark",
|
|
1262
|
+
productName: "MagicMark - Advanced Query Builder"
|
|
1263
|
+
}),
|
|
1264
|
+
signal: controller.signal
|
|
1265
|
+
});
|
|
1266
|
+
clearTimeout(timeoutId);
|
|
1267
|
+
const data = await response.json();
|
|
1268
|
+
if (data.success) {
|
|
1269
|
+
const isValid = data.data.isActive && !data.data.isExpired;
|
|
1270
|
+
const statusInfo = data.data.isExpired ? "EXPIRED" : data.data.isActive ? "ACTIVE" : "INACTIVE";
|
|
1271
|
+
log.info(`[SUCCESS] License verified online: ${statusInfo} (Key: ${licenseKey?.substring(0, 8)}...)`);
|
|
1272
|
+
if (isValid) {
|
|
1273
|
+
const pluginStore = strapi.store({
|
|
1274
|
+
type: "plugin",
|
|
1275
|
+
name: "magic-mark"
|
|
1276
|
+
});
|
|
1277
|
+
await pluginStore.set({ key: "lastValidated", value: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1278
|
+
}
|
|
1279
|
+
return {
|
|
1280
|
+
valid: isValid,
|
|
1281
|
+
data: data.data
|
|
1282
|
+
};
|
|
1283
|
+
} else {
|
|
1284
|
+
log.warn(`[WARN] License verification failed: ${data.message || "Unknown error"} (Key: ${licenseKey?.substring(0, 8)}...)`);
|
|
1285
|
+
return { valid: false, data: null };
|
|
1211
1286
|
}
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1287
|
+
} catch (error) {
|
|
1288
|
+
if (allowGracePeriod) {
|
|
1289
|
+
log.warn(`[WARN] Cannot verify license online: ${error.message} (Key: ${licenseKey?.substring(0, 8)}...)`);
|
|
1290
|
+
log.info(`[GRACE] Grace period active - accepting stored license key`);
|
|
1291
|
+
return { valid: true, data: null, gracePeriod: true };
|
|
1292
|
+
}
|
|
1293
|
+
log.error(`[ERROR] Error verifying license: ${error.message} (Key: ${licenseKey?.substring(0, 8)}...)`);
|
|
1218
1294
|
return { valid: false, data: null };
|
|
1219
1295
|
}
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
}
|
|
1246
|
-
})
|
|
1247
|
-
|
|
1248
|
-
if (data.success) {
|
|
1249
|
-
strapi.log.debug(`[PING] License ping successful: ${data.data?.isActive ? "ACTIVE" : "INACTIVE"} (Key: ${licenseKey?.substring(0, 8)}...)`);
|
|
1250
|
-
return data.data;
|
|
1251
|
-
} else {
|
|
1252
|
-
strapi.log.debug(`[WARN] License ping failed: ${data.message || "Unknown error"} (Key: ${licenseKey?.substring(0, 8)}...)`);
|
|
1296
|
+
},
|
|
1297
|
+
/**
|
|
1298
|
+
* Ping a license (lightweight check)
|
|
1299
|
+
*/
|
|
1300
|
+
async pingLicense(licenseKey) {
|
|
1301
|
+
try {
|
|
1302
|
+
const licenseServerUrl = this.getLicenseServerUrl();
|
|
1303
|
+
const response = await fetch(`${licenseServerUrl}/api/licenses/ping`, {
|
|
1304
|
+
method: "POST",
|
|
1305
|
+
headers: {
|
|
1306
|
+
"Content-Type": "application/json"
|
|
1307
|
+
},
|
|
1308
|
+
body: JSON.stringify({
|
|
1309
|
+
licenseKey,
|
|
1310
|
+
pluginName: "magic-mark",
|
|
1311
|
+
productName: "MagicMark - Advanced Query Builder"
|
|
1312
|
+
})
|
|
1313
|
+
});
|
|
1314
|
+
const data = await response.json();
|
|
1315
|
+
if (data.success) {
|
|
1316
|
+
log.debug(`[PING] License ping successful: ${data.data?.isActive ? "ACTIVE" : "INACTIVE"} (Key: ${licenseKey?.substring(0, 8)}...)`);
|
|
1317
|
+
return data.data;
|
|
1318
|
+
} else {
|
|
1319
|
+
log.debug(`[WARN] License ping failed: ${data.message || "Unknown error"} (Key: ${licenseKey?.substring(0, 8)}...)`);
|
|
1320
|
+
return null;
|
|
1321
|
+
}
|
|
1322
|
+
} catch (error) {
|
|
1323
|
+
log.debug(`License ping error: ${error.message} (Key: ${licenseKey?.substring(0, 8)}...)`);
|
|
1253
1324
|
return null;
|
|
1254
1325
|
}
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1326
|
+
},
|
|
1327
|
+
/**
|
|
1328
|
+
* Get license by key
|
|
1329
|
+
*/
|
|
1330
|
+
async getLicenseByKey(licenseKey) {
|
|
1331
|
+
try {
|
|
1332
|
+
const licenseServerUrl = this.getLicenseServerUrl();
|
|
1333
|
+
const response = await fetch(`${licenseServerUrl}/api/licenses/key/${licenseKey}`);
|
|
1334
|
+
const data = await response.json();
|
|
1335
|
+
if (data.success) {
|
|
1336
|
+
return data.data;
|
|
1337
|
+
}
|
|
1338
|
+
return null;
|
|
1339
|
+
} catch (error) {
|
|
1340
|
+
log.error("[ERROR] Error fetching license by key:", error);
|
|
1341
|
+
return null;
|
|
1270
1342
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1343
|
+
},
|
|
1344
|
+
/**
|
|
1345
|
+
* Get current license data from store (fetches fresh from server)
|
|
1346
|
+
* This is the correct way to get license data with all feature flags
|
|
1347
|
+
*/
|
|
1348
|
+
async getCurrentLicense() {
|
|
1349
|
+
try {
|
|
1350
|
+
const pluginStore = strapi.store({
|
|
1351
|
+
type: "plugin",
|
|
1352
|
+
name: "magic-mark"
|
|
1353
|
+
});
|
|
1354
|
+
const licenseKey = await pluginStore.get({ key: "licenseKey" });
|
|
1355
|
+
if (!licenseKey) {
|
|
1356
|
+
return null;
|
|
1357
|
+
}
|
|
1358
|
+
const license2 = await this.getLicenseByKey(licenseKey);
|
|
1359
|
+
return license2;
|
|
1360
|
+
} catch (error) {
|
|
1361
|
+
log.error("[LICENSE] Error loading current license:", error);
|
|
1289
1362
|
return null;
|
|
1290
1363
|
}
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
});
|
|
1321
|
-
const licenseKey = await pluginStore.get({ key: "licenseKey" });
|
|
1322
|
-
const lastValidated = await pluginStore.get({ key: "lastValidated" });
|
|
1323
|
-
const now = /* @__PURE__ */ new Date();
|
|
1324
|
-
const gracePeriodHours = 24;
|
|
1325
|
-
let withinGracePeriod = false;
|
|
1326
|
-
if (lastValidated) {
|
|
1327
|
-
const lastValidatedDate = new Date(lastValidated);
|
|
1328
|
-
const hoursSinceValidation = (now.getTime() - lastValidatedDate.getTime()) / (1e3 * 60 * 60);
|
|
1329
|
-
withinGracePeriod = hoursSinceValidation < gracePeriodHours;
|
|
1330
|
-
}
|
|
1331
|
-
strapi.log.info("──────────────────────────────────────────────────────────");
|
|
1332
|
-
strapi.log.info(`[STORE] Plugin Store Check:`);
|
|
1333
|
-
if (licenseKey) {
|
|
1334
|
-
strapi.log.info(` [OK] License Key found: ${licenseKey}`);
|
|
1335
|
-
strapi.log.info(` [KEY] Key (short): ${licenseKey.substring(0, 8)}...`);
|
|
1364
|
+
},
|
|
1365
|
+
/**
|
|
1366
|
+
* Start periodic pinging for a license
|
|
1367
|
+
*/
|
|
1368
|
+
startPinging(licenseKey, intervalMinutes = 15) {
|
|
1369
|
+
const intervalMs = intervalMinutes * 60 * 1e3;
|
|
1370
|
+
this.pingLicense(licenseKey);
|
|
1371
|
+
const pingInterval = setInterval(async () => {
|
|
1372
|
+
await this.pingLicense(licenseKey);
|
|
1373
|
+
}, intervalMs);
|
|
1374
|
+
log.info(`[PING] Started pinging license every ${intervalMinutes} minutes`);
|
|
1375
|
+
return pingInterval;
|
|
1376
|
+
},
|
|
1377
|
+
/**
|
|
1378
|
+
* Initialize license guard
|
|
1379
|
+
* Checks for existing license or prompts for creation
|
|
1380
|
+
*/
|
|
1381
|
+
async initialize() {
|
|
1382
|
+
try {
|
|
1383
|
+
log.info("[LICENSE] Initializing License Guard...");
|
|
1384
|
+
const pluginStore = strapi.store({
|
|
1385
|
+
type: "plugin",
|
|
1386
|
+
name: "magic-mark"
|
|
1387
|
+
});
|
|
1388
|
+
const licenseKey = await pluginStore.get({ key: "licenseKey" });
|
|
1389
|
+
const lastValidated = await pluginStore.get({ key: "lastValidated" });
|
|
1390
|
+
const now = /* @__PURE__ */ new Date();
|
|
1391
|
+
const gracePeriodHours = 24;
|
|
1392
|
+
let withinGracePeriod = false;
|
|
1336
1393
|
if (lastValidated) {
|
|
1337
1394
|
const lastValidatedDate = new Date(lastValidated);
|
|
1338
|
-
const
|
|
1339
|
-
|
|
1340
|
-
} else {
|
|
1341
|
-
strapi.log.info(` [TIME] Last validated: Never (Grace: ACTIVE for first ${gracePeriodHours}h)`);
|
|
1395
|
+
const hoursSinceValidation = (now.getTime() - lastValidatedDate.getTime()) / (1e3 * 60 * 60);
|
|
1396
|
+
withinGracePeriod = hoursSinceValidation < gracePeriodHours;
|
|
1342
1397
|
}
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
strapi.log.info("[SUCCESS] License accepted (offline mode / grace period)");
|
|
1398
|
+
log.info("──────────────────────────────────────────────────────────");
|
|
1399
|
+
log.info(`[STORE] Plugin Store Check:`);
|
|
1400
|
+
if (licenseKey) {
|
|
1401
|
+
log.info(` [OK] License Key found: ${licenseKey}`);
|
|
1402
|
+
log.info(` [KEY] Key (short): ${licenseKey.substring(0, 8)}...`);
|
|
1403
|
+
if (lastValidated) {
|
|
1404
|
+
const lastValidatedDate = new Date(lastValidated);
|
|
1405
|
+
const hoursAgo = Math.floor((now.getTime() - lastValidatedDate.getTime()) / (1e3 * 60 * 60));
|
|
1406
|
+
log.info(` [TIME] Last validated: ${hoursAgo}h ago (Grace: ${withinGracePeriod ? "ACTIVE" : "EXPIRED"})`);
|
|
1353
1407
|
} else {
|
|
1354
|
-
|
|
1408
|
+
log.info(` [TIME] Last validated: Never (Grace: ACTIVE for first ${gracePeriodHours}h)`);
|
|
1355
1409
|
}
|
|
1356
|
-
const pingInterval = this.startPinging(licenseKey, 15);
|
|
1357
|
-
strapi.licenseGuard = {
|
|
1358
|
-
licenseKey,
|
|
1359
|
-
pingInterval,
|
|
1360
|
-
data: verification.data
|
|
1361
|
-
};
|
|
1362
|
-
return { valid: true, data: verification.data };
|
|
1363
1410
|
} else {
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1411
|
+
log.info(` [NONE] No license key stored`);
|
|
1412
|
+
}
|
|
1413
|
+
log.info("──────────────────────────────────────────────────────────");
|
|
1414
|
+
if (licenseKey) {
|
|
1415
|
+
log.info("[VERIFY] Verifying stored license key...");
|
|
1416
|
+
const verification = await this.verifyLicense(licenseKey, true);
|
|
1417
|
+
if (verification.valid) {
|
|
1418
|
+
if (verification.gracePeriod) {
|
|
1419
|
+
log.info("[SUCCESS] License accepted (offline mode / grace period)");
|
|
1420
|
+
} else {
|
|
1421
|
+
log.info("[SUCCESS] License is valid and active");
|
|
1422
|
+
}
|
|
1423
|
+
const pingInterval = this.startPinging(licenseKey, 15);
|
|
1424
|
+
strapi.licenseGuard = {
|
|
1425
|
+
licenseKey,
|
|
1426
|
+
pingInterval,
|
|
1427
|
+
data: verification.data
|
|
1428
|
+
};
|
|
1429
|
+
return { valid: true, data: verification.data };
|
|
1430
|
+
} else {
|
|
1431
|
+
log.warn("[WARN] Stored license is invalid or expired");
|
|
1432
|
+
if (!withinGracePeriod) {
|
|
1433
|
+
await pluginStore.delete({ key: "licenseKey" });
|
|
1434
|
+
await pluginStore.delete({ key: "lastValidated" });
|
|
1435
|
+
}
|
|
1368
1436
|
}
|
|
1369
1437
|
}
|
|
1438
|
+
log.warn("[WARN] No valid license found. Plugin will run with limited functionality.");
|
|
1439
|
+
return { valid: false, demo: true };
|
|
1440
|
+
} catch (error) {
|
|
1441
|
+
log.error("[ERROR] Error initializing license guard:", error);
|
|
1442
|
+
return { valid: false, error: error.message };
|
|
1370
1443
|
}
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
} else {
|
|
1395
|
-
strapi.log.error("[ERROR] License key storage verification failed");
|
|
1444
|
+
},
|
|
1445
|
+
/**
|
|
1446
|
+
* Store license key after creation
|
|
1447
|
+
*/
|
|
1448
|
+
async storeLicenseKey(licenseKey) {
|
|
1449
|
+
try {
|
|
1450
|
+
log.info(`[STORE] Storing license key: ${licenseKey}`);
|
|
1451
|
+
const pluginStore = strapi.store({
|
|
1452
|
+
type: "plugin",
|
|
1453
|
+
name: "magic-mark"
|
|
1454
|
+
});
|
|
1455
|
+
await pluginStore.set({ key: "licenseKey", value: licenseKey });
|
|
1456
|
+
await pluginStore.set({ key: "lastValidated", value: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1457
|
+
const stored = await pluginStore.get({ key: "licenseKey" });
|
|
1458
|
+
if (stored === licenseKey) {
|
|
1459
|
+
log.info("[SUCCESS] License key stored and verified successfully");
|
|
1460
|
+
return true;
|
|
1461
|
+
} else {
|
|
1462
|
+
log.error("[ERROR] License key storage verification failed");
|
|
1463
|
+
return false;
|
|
1464
|
+
}
|
|
1465
|
+
} catch (error) {
|
|
1466
|
+
log.error("[ERROR] Error storing license key:", error);
|
|
1396
1467
|
return false;
|
|
1397
1468
|
}
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1469
|
+
},
|
|
1470
|
+
/**
|
|
1471
|
+
* Get bookmark limit based on license tier
|
|
1472
|
+
* @returns Bookmark limit: 10 (free), 50 (premium), -1 (advanced/unlimited)
|
|
1473
|
+
*/
|
|
1474
|
+
async getBookmarkLimit() {
|
|
1475
|
+
try {
|
|
1476
|
+
const licenseGuard = strapi.licenseGuard;
|
|
1477
|
+
const licenseData = licenseGuard?.data;
|
|
1478
|
+
if (licenseData?.featureAdvanced) {
|
|
1479
|
+
return -1;
|
|
1480
|
+
}
|
|
1481
|
+
if (licenseData?.featurePremium) {
|
|
1482
|
+
return 50;
|
|
1483
|
+
}
|
|
1484
|
+
return 10;
|
|
1485
|
+
} catch (error) {
|
|
1486
|
+
log.debug("[LICENSE] Error getting bookmark limit, using free tier");
|
|
1487
|
+
return 10;
|
|
1416
1488
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1489
|
+
},
|
|
1490
|
+
/**
|
|
1491
|
+
* Check if user can create more bookmarks
|
|
1492
|
+
* @param userId - User's documentId
|
|
1493
|
+
* @returns Object with canCreate boolean and current/max counts
|
|
1494
|
+
*/
|
|
1495
|
+
async canCreateBookmark(userId) {
|
|
1496
|
+
try {
|
|
1497
|
+
const limit = await this.getBookmarkLimit();
|
|
1498
|
+
if (limit === -1) {
|
|
1499
|
+
return { canCreate: true, current: 0, max: -1 };
|
|
1500
|
+
}
|
|
1501
|
+
const BOOKMARK_UID2 = "plugin::magic-mark.bookmark";
|
|
1502
|
+
const bookmarks = await strapi.documents(BOOKMARK_UID2).findMany({
|
|
1503
|
+
filters: { creatorId: userId }
|
|
1504
|
+
});
|
|
1505
|
+
const currentCount = bookmarks?.length || 0;
|
|
1506
|
+
if (currentCount >= limit) {
|
|
1507
|
+
return {
|
|
1508
|
+
canCreate: false,
|
|
1509
|
+
current: currentCount,
|
|
1510
|
+
max: limit,
|
|
1511
|
+
message: `Bookmark limit reached (${currentCount}/${limit}). Upgrade to create more bookmarks.`
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
return { canCreate: true, current: currentCount, max: limit };
|
|
1515
|
+
} catch (error) {
|
|
1516
|
+
log.error("[LICENSE] Error checking bookmark limit:", error);
|
|
1517
|
+
return { canCreate: true, current: 0, max: 10 };
|
|
1433
1518
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1519
|
+
},
|
|
1520
|
+
/**
|
|
1521
|
+
* Get current license limits and feature flags
|
|
1522
|
+
* @param userId - User's documentId for bookmark count
|
|
1523
|
+
* @returns License limits object
|
|
1524
|
+
*/
|
|
1525
|
+
async getLicenseLimits(userId) {
|
|
1526
|
+
try {
|
|
1527
|
+
const license2 = await this.getCurrentLicense();
|
|
1528
|
+
log.info("[LICENSE] getLicenseLimits - license exists:", !!license2);
|
|
1529
|
+
if (license2) {
|
|
1530
|
+
log.info("[LICENSE] getLicenseLimits - featurePremium:", license2.featurePremium);
|
|
1531
|
+
log.info("[LICENSE] getLicenseLimits - featureAdvanced:", license2.featureAdvanced);
|
|
1532
|
+
}
|
|
1533
|
+
let tier = "free";
|
|
1534
|
+
if (license2?.featureEnterprise === true) tier = "enterprise";
|
|
1535
|
+
else if (license2?.featureAdvanced === true) tier = "advanced";
|
|
1536
|
+
else if (license2?.featurePremium === true) tier = "premium";
|
|
1537
|
+
const isPremium = tier === "premium" || tier === "advanced" || tier === "enterprise";
|
|
1538
|
+
const isAdvanced = tier === "advanced" || tier === "enterprise";
|
|
1539
|
+
log.info("[LICENSE] getLicenseLimits - detected tier:", tier);
|
|
1540
|
+
const bookmarkCheck = await this.canCreateBookmark(userId);
|
|
1440
1541
|
return {
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1542
|
+
maxBookmarks: bookmarkCheck.max,
|
|
1543
|
+
currentBookmarks: bookmarkCheck.current,
|
|
1544
|
+
canCreate: bookmarkCheck.canCreate,
|
|
1545
|
+
tier,
|
|
1546
|
+
features: {
|
|
1547
|
+
queryHistory: isPremium || isAdvanced,
|
|
1548
|
+
export: isPremium || isAdvanced,
|
|
1549
|
+
analytics: isAdvanced,
|
|
1550
|
+
bulkOperations: isAdvanced,
|
|
1551
|
+
customIntegrations: isAdvanced
|
|
1552
|
+
}
|
|
1553
|
+
};
|
|
1554
|
+
} catch (error) {
|
|
1555
|
+
log.error("[LICENSE] Error getting license limits:", error);
|
|
1556
|
+
return {
|
|
1557
|
+
maxBookmarks: 10,
|
|
1558
|
+
currentBookmarks: 0,
|
|
1559
|
+
canCreate: true,
|
|
1560
|
+
tier: "free",
|
|
1561
|
+
features: {
|
|
1562
|
+
queryHistory: false,
|
|
1563
|
+
export: false,
|
|
1564
|
+
analytics: false,
|
|
1565
|
+
bulkOperations: false,
|
|
1566
|
+
customIntegrations: false
|
|
1567
|
+
}
|
|
1445
1568
|
};
|
|
1446
1569
|
}
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
try {
|
|
1511
|
-
const licenseGuard = strapi.licenseGuard;
|
|
1512
|
-
const licenseData = licenseGuard?.data;
|
|
1513
|
-
const isPremium = licenseData?.featurePremium || false;
|
|
1514
|
-
const isAdvanced = licenseData?.featureAdvanced || false;
|
|
1515
|
-
const featureRequirements = {
|
|
1516
|
-
// Free tier
|
|
1517
|
-
basicBookmarks: "free",
|
|
1518
|
-
basicFilters: "free",
|
|
1519
|
-
// Premium tier
|
|
1520
|
-
extendedBookmarks: "premium",
|
|
1521
|
-
queryHistory: "premium",
|
|
1522
|
-
exportBookmarks: "premium",
|
|
1523
|
-
sharedBookmarks: "premium",
|
|
1524
|
-
// Advanced tier
|
|
1525
|
-
unlimitedBookmarks: "advanced",
|
|
1526
|
-
advancedFilters: "advanced",
|
|
1527
|
-
subGroups: "advanced",
|
|
1528
|
-
bulkOperations: "advanced",
|
|
1529
|
-
analytics: "advanced",
|
|
1530
|
-
customIntegrations: "advanced"
|
|
1531
|
-
};
|
|
1532
|
-
const requiredTier = featureRequirements[feature] || "free";
|
|
1533
|
-
let currentTierLevel = 0;
|
|
1534
|
-
if (isAdvanced) currentTierLevel = 2;
|
|
1535
|
-
else if (isPremium) currentTierLevel = 1;
|
|
1536
|
-
const tierLevels = {
|
|
1537
|
-
free: 0,
|
|
1538
|
-
premium: 1,
|
|
1539
|
-
advanced: 2
|
|
1540
|
-
};
|
|
1541
|
-
return currentTierLevel >= tierLevels[requiredTier];
|
|
1542
|
-
} catch (error) {
|
|
1543
|
-
strapi.log.debug("[LICENSE] Error checking feature:", feature);
|
|
1544
|
-
return false;
|
|
1545
|
-
}
|
|
1546
|
-
},
|
|
1547
|
-
/**
|
|
1548
|
-
* Get current tier name
|
|
1549
|
-
* @returns 'free' | 'premium' | 'advanced'
|
|
1550
|
-
*/
|
|
1551
|
-
getCurrentTier() {
|
|
1552
|
-
try {
|
|
1570
|
+
},
|
|
1571
|
+
/**
|
|
1572
|
+
* Check if a specific feature is available based on license
|
|
1573
|
+
* @param feature - Feature key to check
|
|
1574
|
+
* @returns Boolean indicating if feature is available
|
|
1575
|
+
*/
|
|
1576
|
+
hasFeature(feature) {
|
|
1577
|
+
try {
|
|
1578
|
+
const licenseGuard = strapi.licenseGuard;
|
|
1579
|
+
const licenseData = licenseGuard?.data;
|
|
1580
|
+
const isPremium = licenseData?.featurePremium || false;
|
|
1581
|
+
const isAdvanced = licenseData?.featureAdvanced || false;
|
|
1582
|
+
const featureRequirements = {
|
|
1583
|
+
// Free tier
|
|
1584
|
+
basicBookmarks: "free",
|
|
1585
|
+
basicFilters: "free",
|
|
1586
|
+
// Premium tier
|
|
1587
|
+
extendedBookmarks: "premium",
|
|
1588
|
+
queryHistory: "premium",
|
|
1589
|
+
exportBookmarks: "premium",
|
|
1590
|
+
sharedBookmarks: "premium",
|
|
1591
|
+
// Advanced tier
|
|
1592
|
+
unlimitedBookmarks: "advanced",
|
|
1593
|
+
advancedFilters: "advanced",
|
|
1594
|
+
subGroups: "advanced",
|
|
1595
|
+
bulkOperations: "advanced",
|
|
1596
|
+
analytics: "advanced",
|
|
1597
|
+
customIntegrations: "advanced"
|
|
1598
|
+
};
|
|
1599
|
+
const requiredTier = featureRequirements[feature] || "free";
|
|
1600
|
+
let currentTierLevel = 0;
|
|
1601
|
+
if (isAdvanced) currentTierLevel = 2;
|
|
1602
|
+
else if (isPremium) currentTierLevel = 1;
|
|
1603
|
+
const tierLevels = {
|
|
1604
|
+
free: 0,
|
|
1605
|
+
premium: 1,
|
|
1606
|
+
advanced: 2
|
|
1607
|
+
};
|
|
1608
|
+
return currentTierLevel >= tierLevels[requiredTier];
|
|
1609
|
+
} catch (error) {
|
|
1610
|
+
log.debug("[LICENSE] Error checking feature:", feature);
|
|
1611
|
+
return false;
|
|
1612
|
+
}
|
|
1613
|
+
},
|
|
1614
|
+
/**
|
|
1615
|
+
* Get current tier name
|
|
1616
|
+
* @returns 'free' | 'premium' | 'advanced'
|
|
1617
|
+
*/
|
|
1618
|
+
getCurrentTier() {
|
|
1619
|
+
try {
|
|
1620
|
+
const licenseGuard = strapi.licenseGuard;
|
|
1621
|
+
const licenseData = licenseGuard?.data;
|
|
1622
|
+
if (licenseData?.featureAdvanced) return "advanced";
|
|
1623
|
+
if (licenseData?.featurePremium) return "premium";
|
|
1624
|
+
return "free";
|
|
1625
|
+
} catch (error) {
|
|
1626
|
+
return "free";
|
|
1627
|
+
}
|
|
1628
|
+
},
|
|
1629
|
+
/**
|
|
1630
|
+
* Cleanup on plugin destroy
|
|
1631
|
+
*/
|
|
1632
|
+
cleanup() {
|
|
1553
1633
|
const licenseGuard = strapi.licenseGuard;
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
} catch (error) {
|
|
1559
|
-
return "free";
|
|
1560
|
-
}
|
|
1561
|
-
},
|
|
1562
|
-
/**
|
|
1563
|
-
* Cleanup on plugin destroy
|
|
1564
|
-
*/
|
|
1565
|
-
cleanup() {
|
|
1566
|
-
const licenseGuard = strapi.licenseGuard;
|
|
1567
|
-
if (licenseGuard && licenseGuard.pingInterval) {
|
|
1568
|
-
clearInterval(licenseGuard.pingInterval);
|
|
1569
|
-
strapi.log.info("[STOP] License pinging stopped");
|
|
1634
|
+
if (licenseGuard && licenseGuard.pingInterval) {
|
|
1635
|
+
clearInterval(licenseGuard.pingInterval);
|
|
1636
|
+
log.info("[STOP] License pinging stopped");
|
|
1637
|
+
}
|
|
1570
1638
|
}
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1639
|
+
};
|
|
1640
|
+
};
|
|
1573
1641
|
const services = {
|
|
1574
1642
|
bookmarks: bookmarkService,
|
|
1575
1643
|
"license-guard": licenseGuardService
|