@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 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;