@stamprally/core 0.6.0 → 0.7.0
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/dist/index.cjs +233 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +133 -4
- package/dist/index.d.ts +133 -4
- package/dist/index.js +228 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1880,8 +1880,61 @@ var DEFAULT_SHEET_THEME = {
|
|
|
1880
1880
|
fontFamily: "serif"
|
|
1881
1881
|
};
|
|
1882
1882
|
|
|
1883
|
+
// src/domain/universalModel.ts
|
|
1884
|
+
function toPublicCondition(condition2) {
|
|
1885
|
+
switch (condition2.type) {
|
|
1886
|
+
case "qr":
|
|
1887
|
+
return { type: "qr", qrEntryUrl: condition2.qrEntryUrl ?? "" };
|
|
1888
|
+
case "passcode":
|
|
1889
|
+
return { type: "passcode" };
|
|
1890
|
+
case "gps":
|
|
1891
|
+
return {
|
|
1892
|
+
type: "gps",
|
|
1893
|
+
latitude: condition2.latitude,
|
|
1894
|
+
longitude: condition2.longitude,
|
|
1895
|
+
radiusMeters: condition2.radiusMeters
|
|
1896
|
+
};
|
|
1897
|
+
case "custom":
|
|
1898
|
+
return { type: "custom", validatorName: condition2.validatorName };
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
function toPublicRallyConfig(config) {
|
|
1902
|
+
return {
|
|
1903
|
+
...config,
|
|
1904
|
+
spots: config.spots.map((spot) => ({
|
|
1905
|
+
...spot,
|
|
1906
|
+
conditions: spot.conditions.map(toPublicCondition)
|
|
1907
|
+
})),
|
|
1908
|
+
rewards: config.rewards.map(
|
|
1909
|
+
({ staffPasscode: _staffPasscode, digitalContentUrl: _content, ...reward }) => reward
|
|
1910
|
+
)
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
function isPublicRallyConfig(value) {
|
|
1914
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
1915
|
+
const candidate = value;
|
|
1916
|
+
if (candidate.secretKey !== void 0 || candidate.verificationSecrets !== void 0)
|
|
1917
|
+
return false;
|
|
1918
|
+
return Array.isArray(candidate.spots) && Array.isArray(candidate.rewards) && candidate.spots.every((spot) => {
|
|
1919
|
+
if (typeof spot !== "object" || spot === null || Array.isArray(spot)) return false;
|
|
1920
|
+
const conditions = spot.conditions;
|
|
1921
|
+
return Array.isArray(conditions) && conditions.every((condition2) => {
|
|
1922
|
+
if (typeof condition2 !== "object" || condition2 === null) return false;
|
|
1923
|
+
const type = condition2.type;
|
|
1924
|
+
return type === "qr" || type === "passcode" || type === "gps" || type === "custom";
|
|
1925
|
+
});
|
|
1926
|
+
}) && candidate.rewards.every((reward) => {
|
|
1927
|
+
if (typeof reward !== "object" || reward === null || Array.isArray(reward)) return false;
|
|
1928
|
+
const item = reward;
|
|
1929
|
+
return item.staffPasscode === void 0 && item.digitalContentUrl === void 0;
|
|
1930
|
+
});
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1883
1933
|
// src/domain/publicConfig.ts
|
|
1884
1934
|
function stripSensitiveConfig(config) {
|
|
1935
|
+
if ("spots" in config && !("stamps" in config)) {
|
|
1936
|
+
return toPublicRallyConfig(config);
|
|
1937
|
+
}
|
|
1885
1938
|
const rewards = config.rewards?.map(({ staffPasscode: _staffPasscode, ...reward }) => reward);
|
|
1886
1939
|
return {
|
|
1887
1940
|
...config,
|
|
@@ -1988,6 +2041,180 @@ var THEME_PRESETS = [
|
|
|
1988
2041
|
}
|
|
1989
2042
|
];
|
|
1990
2043
|
|
|
2044
|
+
// src/domain/universalValidation.ts
|
|
2045
|
+
function isObject3(value) {
|
|
2046
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2047
|
+
}
|
|
2048
|
+
function add2(errors, path, code, message) {
|
|
2049
|
+
errors.push({ path, code, message });
|
|
2050
|
+
}
|
|
2051
|
+
function hasText2(value) {
|
|
2052
|
+
if (typeof value === "string") return value.trim() !== "";
|
|
2053
|
+
return isObject3(value) && Object.values(value).some((item) => typeof item === "string" && item.trim() !== "");
|
|
2054
|
+
}
|
|
2055
|
+
function validateCondition2(condition2, path, errors) {
|
|
2056
|
+
if (!isObject3(condition2) || typeof condition2.type !== "string") {
|
|
2057
|
+
add2(errors, path, "INVALID_TYPE", "Condition must have a type.");
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
switch (condition2.type) {
|
|
2061
|
+
case "qr":
|
|
2062
|
+
if (typeof condition2.secretToken !== "string" || condition2.secretToken.trim() === "")
|
|
2063
|
+
add2(errors, `${path}.secretToken`, "REQUIRED", "QR secretToken is required.");
|
|
2064
|
+
if (condition2.qrEntryUrl !== void 0 && typeof condition2.qrEntryUrl !== "string")
|
|
2065
|
+
add2(errors, `${path}.qrEntryUrl`, "INVALID_TYPE", "QR entry URL must be a string.");
|
|
2066
|
+
return;
|
|
2067
|
+
case "passcode":
|
|
2068
|
+
if (typeof condition2.code !== "string" || condition2.code.trim() === "")
|
|
2069
|
+
add2(errors, `${path}.code`, "REQUIRED", "Passcode is required.");
|
|
2070
|
+
return;
|
|
2071
|
+
case "gps":
|
|
2072
|
+
if (typeof condition2.latitude !== "number" || !Number.isFinite(condition2.latitude) || condition2.latitude < -90 || condition2.latitude > 90 || typeof condition2.longitude !== "number" || !Number.isFinite(condition2.longitude) || condition2.longitude < -180 || condition2.longitude > 180)
|
|
2073
|
+
add2(errors, path, "INVALID_COORDINATES", "GPS coordinates are invalid.");
|
|
2074
|
+
if (typeof condition2.radiusMeters !== "number" || !Number.isFinite(condition2.radiusMeters) || condition2.radiusMeters <= 0)
|
|
2075
|
+
add2(errors, `${path}.radiusMeters`, "INVALID_RADIUS", "GPS radius must be positive.");
|
|
2076
|
+
return;
|
|
2077
|
+
case "custom":
|
|
2078
|
+
if (typeof condition2.validatorName !== "string" || condition2.validatorName.trim() === "")
|
|
2079
|
+
add2(errors, `${path}.validatorName`, "REQUIRED", "Custom validatorName is required.");
|
|
2080
|
+
return;
|
|
2081
|
+
default:
|
|
2082
|
+
add2(errors, `${path}.type`, "INVALID_TYPE", "Unsupported verification condition.");
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
function validateDag2(spots, errors) {
|
|
2086
|
+
const graph = /* @__PURE__ */ new Map();
|
|
2087
|
+
for (const spot of spots) {
|
|
2088
|
+
if (!isObject3(spot) || typeof spot.id !== "string") continue;
|
|
2089
|
+
const prerequisites = Array.isArray(spot.prerequisites) ? spot.prerequisites.filter((item) => typeof item === "string") : [];
|
|
2090
|
+
graph.set(spot.id, prerequisites);
|
|
2091
|
+
}
|
|
2092
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
2093
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2094
|
+
const visit = (id) => {
|
|
2095
|
+
if (visiting.has(id)) {
|
|
2096
|
+
add2(
|
|
2097
|
+
errors,
|
|
2098
|
+
`spots.${id}.prerequisites`,
|
|
2099
|
+
"CYCLE_DETECTED",
|
|
2100
|
+
`Dependency cycle detected at '${id}'.`
|
|
2101
|
+
);
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
if (visited.has(id)) return;
|
|
2105
|
+
visiting.add(id);
|
|
2106
|
+
for (const prerequisite of graph.get(id) ?? [])
|
|
2107
|
+
if (graph.has(prerequisite)) visit(prerequisite);
|
|
2108
|
+
visiting.delete(id);
|
|
2109
|
+
visited.add(id);
|
|
2110
|
+
};
|
|
2111
|
+
for (const id of graph.keys()) visit(id);
|
|
2112
|
+
}
|
|
2113
|
+
function validateAdminRallyConfig(value) {
|
|
2114
|
+
const errors = [];
|
|
2115
|
+
if (!isObject3(value))
|
|
2116
|
+
return {
|
|
2117
|
+
valid: false,
|
|
2118
|
+
errors: [{ path: "", code: "INVALID_TYPE", message: "Admin config must be an object." }]
|
|
2119
|
+
};
|
|
2120
|
+
if (typeof value.id !== "string" || value.id.trim() === "")
|
|
2121
|
+
add2(errors, "id", "REQUIRED", "Rally ID is required.");
|
|
2122
|
+
if (typeof value.version !== "string" || value.version.trim() === "")
|
|
2123
|
+
add2(errors, "version", "INVALID_VERSION", "Version is required.");
|
|
2124
|
+
if (!Array.isArray(value.spots)) {
|
|
2125
|
+
add2(errors, "spots", "INVALID_TYPE", "spots must be an array.");
|
|
2126
|
+
} else {
|
|
2127
|
+
const ids = [];
|
|
2128
|
+
value.spots.forEach((spot, index) => {
|
|
2129
|
+
const path = `spots[${index}]`;
|
|
2130
|
+
if (!isObject3(spot)) {
|
|
2131
|
+
add2(errors, path, "INVALID_TYPE", "Spot must be an object.");
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
if (typeof spot.id !== "string" || spot.id.trim() === "")
|
|
2135
|
+
add2(errors, `${path}.id`, "REQUIRED", "Spot ID is required.");
|
|
2136
|
+
else if (ids.includes(spot.id))
|
|
2137
|
+
add2(errors, `${path}.id`, "DUPLICATE_ID", `Duplicate spot ID '${spot.id}'.`);
|
|
2138
|
+
else ids.push(spot.id);
|
|
2139
|
+
if (!hasText2(spot.name)) add2(errors, `${path}.name`, "REQUIRED", "Spot name is required.");
|
|
2140
|
+
if (typeof spot.orderIndex !== "number" || !Number.isInteger(spot.orderIndex))
|
|
2141
|
+
add2(errors, `${path}.orderIndex`, "INVALID_TYPE", "orderIndex must be an integer.");
|
|
2142
|
+
if (!Array.isArray(spot.conditions) || spot.conditions.length === 0)
|
|
2143
|
+
add2(errors, `${path}.conditions`, "REQUIRED", "At least one condition is required.");
|
|
2144
|
+
else {
|
|
2145
|
+
spot.conditions.forEach((condition2, conditionIndex) => {
|
|
2146
|
+
validateCondition2(condition2, `${path}.conditions[${conditionIndex}]`, errors);
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
2149
|
+
});
|
|
2150
|
+
validateDag2(value.spots, errors);
|
|
2151
|
+
}
|
|
2152
|
+
if (!Array.isArray(value.rewards))
|
|
2153
|
+
add2(errors, "rewards", "INVALID_TYPE", "rewards must be an array.");
|
|
2154
|
+
else {
|
|
2155
|
+
const ids = [];
|
|
2156
|
+
value.rewards.forEach((reward, index) => {
|
|
2157
|
+
const path = `rewards[${index}]`;
|
|
2158
|
+
if (!isObject3(reward)) {
|
|
2159
|
+
add2(errors, path, "INVALID_TYPE", "Reward must be an object.");
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
if (typeof reward.id !== "string" || reward.id.trim() === "")
|
|
2163
|
+
add2(errors, `${path}.id`, "REQUIRED", "Reward ID is required.");
|
|
2164
|
+
else if (ids.includes(reward.id))
|
|
2165
|
+
add2(errors, `${path}.id`, "DUPLICATE_ID", `Duplicate reward ID '${reward.id}'.`);
|
|
2166
|
+
else ids.push(reward.id);
|
|
2167
|
+
if (!hasText2(reward.title))
|
|
2168
|
+
add2(errors, `${path}.title`, "REQUIRED", "Reward title is required.");
|
|
2169
|
+
if (typeof reward.requiredStampCount !== "number" || !Number.isInteger(reward.requiredStampCount) || reward.requiredStampCount < 0)
|
|
2170
|
+
add2(
|
|
2171
|
+
errors,
|
|
2172
|
+
`${path}.requiredStampCount`,
|
|
2173
|
+
"INVALID_REWARD",
|
|
2174
|
+
"requiredStampCount must be a non-negative integer."
|
|
2175
|
+
);
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
2178
|
+
return { valid: errors.length === 0, errors };
|
|
2179
|
+
}
|
|
2180
|
+
function validatePublicRallyConfig(value) {
|
|
2181
|
+
const errors = [];
|
|
2182
|
+
if (!isObject3(value))
|
|
2183
|
+
return {
|
|
2184
|
+
valid: false,
|
|
2185
|
+
errors: [{ path: "", code: "INVALID_TYPE", message: "Public config must be an object." }]
|
|
2186
|
+
};
|
|
2187
|
+
if ("secretKey" in value || "verificationSecrets" in value)
|
|
2188
|
+
add2(
|
|
2189
|
+
errors,
|
|
2190
|
+
"",
|
|
2191
|
+
"SECRET_IN_PUBLIC_CONFIG",
|
|
2192
|
+
"Public config must not contain server verification secrets."
|
|
2193
|
+
);
|
|
2194
|
+
if (!Array.isArray(value.spots)) add2(errors, "spots", "INVALID_TYPE", "spots must be an array.");
|
|
2195
|
+
else
|
|
2196
|
+
value.spots.forEach((spot, index) => {
|
|
2197
|
+
if (!isObject3(spot) || !Array.isArray(spot.conditions)) return;
|
|
2198
|
+
spot.conditions.forEach((condition2, conditionIndex) => {
|
|
2199
|
+
if (!isObject3(condition2)) return;
|
|
2200
|
+
if ("secretToken" in condition2 || "code" in condition2 || "secretParams" in condition2)
|
|
2201
|
+
add2(
|
|
2202
|
+
errors,
|
|
2203
|
+
`spots[${index}].conditions[${conditionIndex}]`,
|
|
2204
|
+
"SECRET_IN_PUBLIC_CONFIG",
|
|
2205
|
+
"Public condition contains verification secret material."
|
|
2206
|
+
);
|
|
2207
|
+
});
|
|
2208
|
+
});
|
|
2209
|
+
return { valid: errors.length === 0, errors };
|
|
2210
|
+
}
|
|
2211
|
+
function isAdminRallyConfig(value) {
|
|
2212
|
+
return validateAdminRallyConfig(value).valid;
|
|
2213
|
+
}
|
|
2214
|
+
function isPublicRallyConfigShape(value) {
|
|
2215
|
+
return validatePublicRallyConfig(value).valid;
|
|
2216
|
+
}
|
|
2217
|
+
|
|
1991
2218
|
// src/security/snapshotToken.ts
|
|
1992
2219
|
var encoder2 = new TextEncoder();
|
|
1993
2220
|
function cryptoApi2() {
|
|
@@ -2128,8 +2355,11 @@ exports.evaluateConditionDetailed = evaluateConditionDetailed;
|
|
|
2128
2355
|
exports.exportProgressToken = exportProgressToken;
|
|
2129
2356
|
exports.getCurrentGeoContext = getCurrentGeoContext;
|
|
2130
2357
|
exports.importProgressToken = importProgressToken;
|
|
2358
|
+
exports.isAdminRallyConfig = isAdminRallyConfig;
|
|
2131
2359
|
exports.isGeolocationSupported = isGeolocationSupported;
|
|
2132
2360
|
exports.isNfcSupported = isNfcSupported;
|
|
2361
|
+
exports.isPublicRallyConfig = isPublicRallyConfig;
|
|
2362
|
+
exports.isPublicRallyConfigShape = isPublicRallyConfigShape;
|
|
2133
2363
|
exports.isQrSupported = isQrSupported;
|
|
2134
2364
|
exports.isRewardState = isRewardState;
|
|
2135
2365
|
exports.isStampRallyState = isStampRallyState;
|
|
@@ -2143,6 +2373,9 @@ exports.reconcileRewardStates = reconcileRewardStates;
|
|
|
2143
2373
|
exports.resolveLocalizedText = resolveLocalizedText;
|
|
2144
2374
|
exports.stripSensitiveConfig = stripSensitiveConfig;
|
|
2145
2375
|
exports.toLocalizedString = toLocalizedString;
|
|
2376
|
+
exports.toPublicRallyConfig = toPublicRallyConfig;
|
|
2377
|
+
exports.validateAdminRallyConfig = validateAdminRallyConfig;
|
|
2378
|
+
exports.validatePublicRallyConfig = validatePublicRallyConfig;
|
|
2146
2379
|
exports.validateRallyConfig = validateRallyConfig;
|
|
2147
2380
|
exports.verifyPasscode = verifyPasscode;
|
|
2148
2381
|
exports.verifySecureToken = verifySecureToken;
|