@sudobility/sudojo_lib 0.0.204 → 0.0.205

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.
@@ -73,6 +73,10 @@ export declare function parseShareParams(params: ShareParamsSource): ParsedShare
73
73
  * - Localhost: kept as-is (web app and API share the same server in dev)
74
74
  * - Production: strips the `api.` subdomain
75
75
  * (e.g. `https://api.sudojo.com` → `https://sudojo.com`)
76
+ *
77
+ * Built from the URL's parts rather than by assigning `url.hostname`: React
78
+ * Native's `URL` has no setters, and the assignment did nothing there, so
79
+ * every share link from the app pointed at the API (`https://api.sudojo.com/play`).
76
80
  */
77
81
  export declare function getWebUrl(apiBaseUrl: string): string;
78
82
  //# sourceMappingURL=shareUrl.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"shareUrl.d.ts","sourceRoot":"","sources":["../../src/utils/shareUrl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAUH,kCAAkC;AAClC,MAAM,MAAM,YAAY,GACpB,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,OAAO,GACP,YAAY,GACZ,WAAW,CAAC;AAEhB,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sDAAsD;IACtD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6FAA6F;IAC7F,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,OAAO,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,6EAA6E;AAC7E,MAAM,MAAM,iBAAiB,GACzB,eAAe,GACf,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AA+B9C;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAyB5D;AAOD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,iBAAiB,GACxB,iBAAiB,GAAG,IAAI,CA6B1B;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAUpD"}
1
+ {"version":3,"file":"shareUrl.d.ts","sourceRoot":"","sources":["../../src/utils/shareUrl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAUH,kCAAkC;AAClC,MAAM,MAAM,YAAY,GACpB,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,OAAO,GACP,YAAY,GACZ,WAAW,CAAC;AAEhB,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sDAAsD;IACtD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6FAA6F;IAC7F,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,OAAO,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,6EAA6E;AAC7E,MAAM,MAAM,iBAAiB,GACzB,eAAe,GACf,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AA+B9C;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAyB5D;AAOD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,iBAAiB,GACxB,iBAAiB,GAAG,IAAI,CA6B1B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAWpD"}
@@ -127,14 +127,18 @@ export function parseShareParams(params) {
127
127
  * - Localhost: kept as-is (web app and API share the same server in dev)
128
128
  * - Production: strips the `api.` subdomain
129
129
  * (e.g. `https://api.sudojo.com` → `https://sudojo.com`)
130
+ *
131
+ * Built from the URL's parts rather than by assigning `url.hostname`: React
132
+ * Native's `URL` has no setters, and the assignment did nothing there, so
133
+ * every share link from the app pointed at the API (`https://api.sudojo.com/play`).
130
134
  */
131
135
  export function getWebUrl(apiBaseUrl) {
132
136
  try {
133
137
  const url = new URL(apiBaseUrl);
134
- if (url.hostname !== 'localhost' && url.hostname.startsWith('api.')) {
135
- url.hostname = url.hostname.replace(/^api\./, '');
136
- }
137
- return url.origin;
138
+ const host = url.hostname !== 'localhost' && url.hostname.startsWith('api.')
139
+ ? url.hostname.slice('api.'.length)
140
+ : url.hostname;
141
+ return `${url.protocol}//${host}${url.port ? `:${url.port}` : ''}`;
138
142
  }
139
143
  catch {
140
144
  return DEFAULT_DOMAIN;
@@ -1 +1 @@
1
- {"version":3,"file":"shareUrl.js","sourceRoot":"","sources":["../../src/utils/shareUrl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAEvD,MAAM,cAAc,GAAG,oBAAoB,CAAC;AAE5C,qCAAqC;AACrC,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AA+CnC,SAAS,cAAc,CAAC,MAAc,EAAE,MAAsB;IAC5D,MAAM,YAAY,GAAG,IAAI,eAAe,EAAE,CAAC;IAC3C,IAAI,MAAM,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC;QACzB,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAClD,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;IACxC,CAAC;IACD,IAAI,MAAM,CAAC,eAAe,IAAI,IAAI,EAAE,CAAC;QACnC,YAAY,CAAC,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC;IACtE,CAAC;IAED,IAAI,GAAG,GAAG,GAAG,MAAM,gBAAgB,YAAY,CAAC,QAAQ,EAAE,EAAE,CAAC;IAE7D,8DAA8D;IAC9D,IAAI,MAAM,CAAC,WAAW,IAAI,IAAI,EAAE,CAAC;QAC/B,GAAG,IAAI,gBAAgB,MAAM,CAAC,WAAW,EAAE,CAAC;IAC9C,CAAC;IACD,mBAAmB;IACnB,IAAI,MAAM,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;QACxB,GAAG,IAAI,SAAS,MAAM,CAAC,IAAI,EAAE,CAAC;IAChC,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,MAAsB;IAClD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,cAAc,CAAC;IAE/C,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,OAAO;YACV,OAAO,GAAG,MAAM,aAAa,CAAC;QAChC,KAAK,QAAQ;YACX,OAAO,GAAG,MAAM,OAAO,CAAC;QAC1B,KAAK,MAAM;YACT,OAAO,MAAM,CAAC,KAAK,IAAI,IAAI;gBACzB,CAAC,CAAC,GAAG,MAAM,SAAS,MAAM,CAAC,KAAK,EAAE;gBAClC,CAAC,CAAC,GAAG,MAAM,OAAO,CAAC;QACvB,KAAK,OAAO;YACV,OAAO,GAAG,MAAM,aAAa,CAAC;QAChC,KAAK,YAAY;YACf,OAAO,GAAG,MAAM,aAAa,CAAC;QAChC,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,MAAM,IAAI,GAAG,wBAAwB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACnD,IAAI,CAAC,IAAI;gBAAE,OAAO,GAAG,MAAM,aAAa,CAAC;YACzC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/D,OAAO,GAAG,MAAM,eAAe,kBAAkB,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC;QACnE,CAAC;QACD,KAAK,QAAQ;YACX,OAAO,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,MAAyB,EAAE,IAAY;IACxD,IAAI,MAAM,YAAY,eAAe;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC/D,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;AAC9B,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAAyB;IAEzB,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAE/C,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE1C,MAAM,MAAM,GAAsB;QAChC,QAAQ;QACR,IAAI,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,WAAW;QAC9C,WAAW,EAAE,SAAS,CAAC,MAAM,EAAE,aAAa,CAAC,IAAI,EAAE;QACnD,eAAe,EAAE,SAAS,CAAC,MAAM,EAAE,iBAAiB,CAAC,KAAK,MAAM;KACjE,CAAC;IACF,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC/B,IAAI,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;YACxB,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;QACvB,CAAC;IACH,CAAC;IACD,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;QAChC,IAAI,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;YAC9C,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC;QACxB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,UAAkB;IAC1C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;QAChC,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,IAAI,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YACpE,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACpD,CAAC;QACD,OAAO,GAAG,CAAC,MAAM,CAAC;IACpB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,cAAc,CAAC;IACxB,CAAC;AACH,CAAC","sourcesContent":["/**\n * Share URL builder and parser for Sudojo sharing.\n *\n * Paths match the web app's routes (the RN app's deep-link config accepts the\n * same paths):\n *\n * | type | URL |\n * |--------------|---------------------------------------------------------|\n * | `daily` | `/play/daily` (bare `/daily` has no web route) |\n * | `puzzle` | `/play/puzzle?level=&original=&user=&autopencilmarks=&pencilmarks=&hint=` |\n * | `levels` | `/play` (the level list) |\n * | `play` | `/play/<level>`, or `/play` without a level |\n * | `enter` | `/play/enter` |\n * | `techniques` | `/techniques` |\n * | `technique` | `/techniques/<canonical path>[?hint=<step>]` |\n *\n * Pencilmarks are appended as a raw comma-separated string (not URL-encoded)\n * to keep URLs readable.\n */\n\nimport { isValidLevel } from '@sudobility/sudojo_types';\nimport { toCanonicalTechniquePath } from './technique';\n\nconst DEFAULT_DOMAIN = 'https://sudojo.com';\n\n/** An empty 81-cell input string. */\nconst EMPTY_INPUT = '0'.repeat(81);\n\n/** What a share URL points at. */\nexport type ShareUrlType =\n | 'daily'\n | 'puzzle'\n | 'levels'\n | 'play'\n | 'enter'\n | 'techniques'\n | 'technique';\n\nexport interface ShareUrlParams {\n type: ShareUrlType;\n /** 81-char original puzzle string (`puzzle`) */\n original?: string;\n /** 81-char user input string (`puzzle`) */\n user?: string;\n /** Comma-separated pencilmarks string, 81 cells, 80 commas (`puzzle`) */\n pencilmarks?: string;\n /** Whether auto-pencilmarks are enabled (`puzzle`) */\n autopencilmarks?: boolean;\n /** Difficulty level, 1-based (`puzzle`, `play`) */\n level?: number;\n /** Hint step index, 0-based. Only included when a hint is active (`puzzle`, `technique`). */\n hint?: number;\n /** Technique path, API or canonical; shared as the canonical slug (`technique`) */\n path?: string;\n /** Base domain (default: https://sudojo.com) */\n domain?: string;\n}\n\nexport interface ParsedShareParams {\n original: string;\n /** Player input; 81 zeros when the URL has none (a bare puzzle share). */\n user: string;\n pencilmarks: string;\n autopencilmarks: boolean;\n level?: number;\n hint?: number;\n}\n\n/** Query values from URLSearchParams or a plain object (RN route params). */\nexport type ShareParamsSource =\n | URLSearchParams\n | Record<string, string | null | undefined>;\n\nfunction buildPuzzleUrl(domain: string, params: ShareUrlParams): string {\n const searchParams = new URLSearchParams();\n if (params.level != null) {\n searchParams.set('level', String(params.level));\n }\n if (params.original) {\n searchParams.set('original', params.original);\n }\n if (params.user) {\n searchParams.set('user', params.user);\n }\n if (params.autopencilmarks != null) {\n searchParams.set('autopencilmarks', String(params.autopencilmarks));\n }\n\n let url = `${domain}/play/puzzle?${searchParams.toString()}`;\n\n // Append pencilmarks raw (commas don't need encoding in URLs)\n if (params.pencilmarks != null) {\n url += `&pencilmarks=${params.pencilmarks}`;\n }\n // Append hint step\n if (params.hint != null) {\n url += `&hint=${params.hint}`;\n }\n\n return url;\n}\n\n/**\n * Build a share URL. See the table at the top of this file for each type.\n *\n * For `puzzle`, pencilmarks are appended directly to the URL (without\n * URLSearchParams encoding) so commas remain as literal commas.\n */\nexport function buildShareUrl(params: ShareUrlParams): string {\n const domain = params.domain ?? DEFAULT_DOMAIN;\n\n switch (params.type) {\n case 'daily':\n return `${domain}/play/daily`;\n case 'levels':\n return `${domain}/play`;\n case 'play':\n return params.level != null\n ? `${domain}/play/${params.level}`\n : `${domain}/play`;\n case 'enter':\n return `${domain}/play/enter`;\n case 'techniques':\n return `${domain}/techniques`;\n case 'technique': {\n const slug = toCanonicalTechniquePath(params.path);\n if (!slug) return `${domain}/techniques`;\n const hint = params.hint != null ? `?hint=${params.hint}` : '';\n return `${domain}/techniques/${encodeURIComponent(slug)}${hint}`;\n }\n case 'puzzle':\n return buildPuzzleUrl(domain, params);\n }\n}\n\nfunction readParam(source: ShareParamsSource, name: string): string | null {\n if (source instanceof URLSearchParams) return source.get(name);\n return source[name] ?? null;\n}\n\n/**\n * Parse share URL query parameters into game state.\n *\n * Accepts URLSearchParams (web) or a plain object of strings (RN route\n * params). Returns null if `original` is missing. A missing `user` (a bare\n * puzzle share, e.g. a freshly entered puzzle) becomes 81 zeros.\n * `level` is kept only when it is a valid level (1-12); `hint` only when it\n * is a non-negative integer. When `level` is absent, callers fall back to the\n * level the solver rates the puzzle at (`parsed.level ?? validatedLevel`).\n */\nexport function parseShareParams(\n params: ShareParamsSource\n): ParsedShareParams | null {\n const original = readParam(params, 'original');\n\n if (!original) {\n return null;\n }\n\n const levelStr = readParam(params, 'level');\n const hintStr = readParam(params, 'hint');\n\n const result: ParsedShareParams = {\n original,\n user: readParam(params, 'user') || EMPTY_INPUT,\n pencilmarks: readParam(params, 'pencilmarks') ?? '',\n autopencilmarks: readParam(params, 'autopencilmarks') === 'true',\n };\n if (levelStr) {\n const level = Number(levelStr);\n if (isValidLevel(level)) {\n result.level = level;\n }\n }\n if (hintStr) {\n const hintNum = Number(hintStr);\n if (Number.isInteger(hintNum) && hintNum >= 0) {\n result.hint = hintNum;\n }\n }\n return result;\n}\n\n/**\n * Derive the web app URL from an API base URL.\n *\n * - Localhost: kept as-is (web app and API share the same server in dev)\n * - Production: strips the `api.` subdomain\n * (e.g. `https://api.sudojo.com` → `https://sudojo.com`)\n */\nexport function getWebUrl(apiBaseUrl: string): string {\n try {\n const url = new URL(apiBaseUrl);\n if (url.hostname !== 'localhost' && url.hostname.startsWith('api.')) {\n url.hostname = url.hostname.replace(/^api\\./, '');\n }\n return url.origin;\n } catch {\n return DEFAULT_DOMAIN;\n }\n}\n"]}
1
+ {"version":3,"file":"shareUrl.js","sourceRoot":"","sources":["../../src/utils/shareUrl.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Share URL builder and parser for Sudojo sharing.\n *\n * Paths match the web app's routes (the RN app's deep-link config accepts the\n * same paths):\n *\n * | type | URL |\n * |--------------|---------------------------------------------------------|\n * | `daily` | `/play/daily` (bare `/daily` has no web route) |\n * | `puzzle` | `/play/puzzle?level=&original=&user=&autopencilmarks=&pencilmarks=&hint=` |\n * | `levels` | `/play` (the level list) |\n * | `play` | `/play/<level>`, or `/play` without a level |\n * | `enter` | `/play/enter` |\n * | `techniques` | `/techniques` |\n * | `technique` | `/techniques/<canonical path>[?hint=<step>]` |\n *\n * Pencilmarks are appended as a raw comma-separated string (not URL-encoded)\n * to keep URLs readable.\n */\n\nimport { isValidLevel } from '@sudobility/sudojo_types';\nimport { toCanonicalTechniquePath } from './technique';\n\nconst DEFAULT_DOMAIN = 'https://sudojo.com';\n\n/** An empty 81-cell input string. */\nconst EMPTY_INPUT = '0'.repeat(81);\n\n/** What a share URL points at. */\nexport type ShareUrlType =\n | 'daily'\n | 'puzzle'\n | 'levels'\n | 'play'\n | 'enter'\n | 'techniques'\n | 'technique';\n\nexport interface ShareUrlParams {\n type: ShareUrlType;\n /** 81-char original puzzle string (`puzzle`) */\n original?: string;\n /** 81-char user input string (`puzzle`) */\n user?: string;\n /** Comma-separated pencilmarks string, 81 cells, 80 commas (`puzzle`) */\n pencilmarks?: string;\n /** Whether auto-pencilmarks are enabled (`puzzle`) */\n autopencilmarks?: boolean;\n /** Difficulty level, 1-based (`puzzle`, `play`) */\n level?: number;\n /** Hint step index, 0-based. Only included when a hint is active (`puzzle`, `technique`). */\n hint?: number;\n /** Technique path, API or canonical; shared as the canonical slug (`technique`) */\n path?: string;\n /** Base domain (default: https://sudojo.com) */\n domain?: string;\n}\n\nexport interface ParsedShareParams {\n original: string;\n /** Player input; 81 zeros when the URL has none (a bare puzzle share). */\n user: string;\n pencilmarks: string;\n autopencilmarks: boolean;\n level?: number;\n hint?: number;\n}\n\n/** Query values from URLSearchParams or a plain object (RN route params). */\nexport type ShareParamsSource =\n | URLSearchParams\n | Record<string, string | null | undefined>;\n\nfunction buildPuzzleUrl(domain: string, params: ShareUrlParams): string {\n const searchParams = new URLSearchParams();\n if (params.level != null) {\n searchParams.set('level', String(params.level));\n }\n if (params.original) {\n searchParams.set('original', params.original);\n }\n if (params.user) {\n searchParams.set('user', params.user);\n }\n if (params.autopencilmarks != null) {\n searchParams.set('autopencilmarks', String(params.autopencilmarks));\n }\n\n let url = `${domain}/play/puzzle?${searchParams.toString()}`;\n\n // Append pencilmarks raw (commas don't need encoding in URLs)\n if (params.pencilmarks != null) {\n url += `&pencilmarks=${params.pencilmarks}`;\n }\n // Append hint step\n if (params.hint != null) {\n url += `&hint=${params.hint}`;\n }\n\n return url;\n}\n\n/**\n * Build a share URL. See the table at the top of this file for each type.\n *\n * For `puzzle`, pencilmarks are appended directly to the URL (without\n * URLSearchParams encoding) so commas remain as literal commas.\n */\nexport function buildShareUrl(params: ShareUrlParams): string {\n const domain = params.domain ?? DEFAULT_DOMAIN;\n\n switch (params.type) {\n case 'daily':\n return `${domain}/play/daily`;\n case 'levels':\n return `${domain}/play`;\n case 'play':\n return params.level != null\n ? `${domain}/play/${params.level}`\n : `${domain}/play`;\n case 'enter':\n return `${domain}/play/enter`;\n case 'techniques':\n return `${domain}/techniques`;\n case 'technique': {\n const slug = toCanonicalTechniquePath(params.path);\n if (!slug) return `${domain}/techniques`;\n const hint = params.hint != null ? `?hint=${params.hint}` : '';\n return `${domain}/techniques/${encodeURIComponent(slug)}${hint}`;\n }\n case 'puzzle':\n return buildPuzzleUrl(domain, params);\n }\n}\n\nfunction readParam(source: ShareParamsSource, name: string): string | null {\n if (source instanceof URLSearchParams) return source.get(name);\n return source[name] ?? null;\n}\n\n/**\n * Parse share URL query parameters into game state.\n *\n * Accepts URLSearchParams (web) or a plain object of strings (RN route\n * params). Returns null if `original` is missing. A missing `user` (a bare\n * puzzle share, e.g. a freshly entered puzzle) becomes 81 zeros.\n * `level` is kept only when it is a valid level (1-12); `hint` only when it\n * is a non-negative integer. When `level` is absent, callers fall back to the\n * level the solver rates the puzzle at (`parsed.level ?? validatedLevel`).\n */\nexport function parseShareParams(\n params: ShareParamsSource\n): ParsedShareParams | null {\n const original = readParam(params, 'original');\n\n if (!original) {\n return null;\n }\n\n const levelStr = readParam(params, 'level');\n const hintStr = readParam(params, 'hint');\n\n const result: ParsedShareParams = {\n original,\n user: readParam(params, 'user') || EMPTY_INPUT,\n pencilmarks: readParam(params, 'pencilmarks') ?? '',\n autopencilmarks: readParam(params, 'autopencilmarks') === 'true',\n };\n if (levelStr) {\n const level = Number(levelStr);\n if (isValidLevel(level)) {\n result.level = level;\n }\n }\n if (hintStr) {\n const hintNum = Number(hintStr);\n if (Number.isInteger(hintNum) && hintNum >= 0) {\n result.hint = hintNum;\n }\n }\n return result;\n}\n\n/**\n * Derive the web app URL from an API base URL.\n *\n * - Localhost: kept as-is (web app and API share the same server in dev)\n * - Production: strips the `api.` subdomain\n * (e.g. `https://api.sudojo.com` → `https://sudojo.com`)\n *\n * Built from the URL's parts rather than by assigning `url.hostname`: React\n * Native's `URL` has no setters, and the assignment did nothing there, so\n * every share link from the app pointed at the API (`https://api.sudojo.com/play`).\n */\nexport function getWebUrl(apiBaseUrl: string): string {\n try {\n const url = new URL(apiBaseUrl);\n const host =\n url.hostname !== 'localhost' && url.hostname.startsWith('api.')\n ? url.hostname.slice('api.'.length)\n : url.hostname;\n return `${url.protocol}//${host}${url.port ? `:${url.port}` : ''}`;\n } catch {\n return DEFAULT_DOMAIN;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sudobility/sudojo_lib",
3
- "version": "0.0.204",
3
+ "version": "0.0.205",
4
4
  "description": "Business logic library for Sudojo app",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",