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