@geniee-inc/sfa-kit 1.17.0 → 1.19.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.
Files changed (39) hide show
  1. package/GUIDE.md +34 -14
  2. package/package.json +1 -1
  3. package/src/bin/sfa-kit-bundle.js +16 -8
  4. package/src/bundle.d.ts +1 -2
  5. package/src/index.d.ts +1 -1
  6. package/src/lib/auth/resolve-api-base.d.ts +6 -4
  7. package/src/lib/auth/resolve-api-base.js +24 -5
  8. package/src/lib/auth/useEmbedSession.d.ts +2 -1
  9. package/src/lib/auth/useEmbedSession.js +196 -26
  10. package/src/lib/bundle/build-static-bundle.d.ts +37 -11
  11. package/src/lib/bundle/build-static-bundle.js +75 -35
  12. package/src/lib/bundle/request-signature.d.ts +13 -1
  13. package/src/lib/bundle/request-signature.js +21 -6
  14. package/src/lib/components/FieldRenderer.d.ts +2 -1
  15. package/src/lib/components/FieldRenderer.js +32 -19
  16. package/src/lib/components/RecordList.js +10 -0
  17. package/src/lib/components/RecordView.js +27 -7
  18. package/src/lib/components/fieldDisplayText.d.ts +11 -0
  19. package/src/lib/components/fieldDisplayText.js +51 -0
  20. package/src/lib/hooks/useFieldTypes.d.ts +5 -0
  21. package/src/lib/hooks/useFieldTypes.js +5 -0
  22. package/src/lib/hooks/useObjectCatalog.d.ts +6 -0
  23. package/src/lib/hooks/useObjectCatalog.js +6 -0
  24. package/src/lib/hooks/useRecord.d.ts +20 -0
  25. package/src/lib/hooks/useRecord.js +53 -0
  26. package/src/lib/provider/SfaClient.d.ts +13 -0
  27. package/src/lib/provider/SfaClient.js +25 -8
  28. package/src/lib/provider/SfaProvider.d.ts +2 -2
  29. package/src/lib/provider/SfaProvider.js +1 -1
  30. package/src/lib/provider/types.d.ts +12 -1
  31. package/src/lib/signing/compute-signature.d.ts +4 -2
  32. package/src/lib/signing/compute-signature.js +4 -2
  33. package/src/lib/signing/create-manifest.d.ts +8 -6
  34. package/src/lib/signing/create-manifest.js +8 -6
  35. package/src/lib/signing/index.d.ts +1 -1
  36. package/src/lib/signing/types.d.ts +33 -4
  37. package/src/lib/version.d.ts +1 -1
  38. package/src/lib/version.js +1 -1
  39. package/src/signing.d.ts +2 -2
package/GUIDE.md CHANGED
@@ -25,16 +25,27 @@ Three additional subpaths are available: `@geniee-inc/sfa-kit/signing` exposes b
25
25
 
26
26
  ### Static bundle manifest (custom-tab uploads)
27
27
 
28
- A page uploaded through GENIEE's SFA/CRM settings UI as a custom tab is a framework-free static bundle — `index.html` + `manifest.json` + `assets/` — not a Next.js app. Its `manifest.json` carries the three fields Keystone's `BundleSignatureVerifier` reads: `studio_version`, `content_hash`, `signature`.
28
+ A page uploaded through GENIEE's SFA/CRM settings UI as a custom tab is a framework-free static bundle — `index.html` + `manifest.json` + `assets/` — not a Next.js app. Its `manifest.json` carries three fields:
29
+
30
+ | Field | Signed | Meaning |
31
+ | ----------------- | ------ | -------------------------------------------------------------------------------------------- |
32
+ | `content_hash` | Yes | SHA-256 over every other file in the bundle (see `computeContentHash`) |
33
+ | `signature` | - | HMAC-SHA-256 of `content_hash`, computed by Keystone |
34
+ | `sfa_kit_version` | No | The sfa-kit version that ran the build. Keystone stores it with the page for troubleshooting |
35
+
36
+ Editing any file after signing changes `content_hash`, and a new `content_hash` needs a new signature that only Keystone can compute, so an edited bundle fails verification.
37
+
38
+ A manifest that also carries `studio_version` is the legacy format: its signature covers `"{studio_version}:{content_hash}"`. Keystone accepts both and tells them apart by whether `studio_version` is present.
29
39
 
30
40
  **The signing key lives only in Keystone, so a page build never computes the signature itself.** It hashes the bundle locally and asks Keystone to sign:
31
41
 
32
42
  ```ts
43
+ import pkg from '@geniee-inc/sfa-kit/package.json';
33
44
  import {
34
45
  readBundleFiles,
35
46
  computeContentHash,
36
47
  } from '@geniee-inc/sfa-kit/signing';
37
- import type { BundleManifest } from '@geniee-inc/sfa-kit/signing';
48
+ import type { ContentHashManifest } from '@geniee-inc/sfa-kit/signing';
38
49
 
39
50
  const files = await readBundleFiles('dist');
40
51
  const contentHash = computeContentHash(files);
@@ -47,16 +58,13 @@ const response = await fetch(`${apiBase}/v1/pages/sign`, {
47
58
  'Content-Type': 'application/json',
48
59
  Authorization: `Bearer ${accessToken}`,
49
60
  },
50
- body: JSON.stringify({
51
- studio_version: studioVersion,
52
- content_hash: contentHash,
53
- }),
61
+ body: JSON.stringify({ content_hash: contentHash }),
54
62
  });
55
63
  if (!response.ok) throw new Error(`Signing failed: ${response.status}`);
56
64
  const { signature } = (await response.json()) as { signature: string };
57
65
 
58
- const manifest: BundleManifest = {
59
- studio_version: studioVersion, // the Studio build version, NOT the sfa-kit npm version
66
+ const manifest: ContentHashManifest = {
67
+ sfa_kit_version: pkg.version,
60
68
  content_hash: contentHash,
61
69
  signature,
62
70
  };
@@ -72,7 +80,6 @@ import { buildStaticBundle } from '@geniee-inc/sfa-kit/bundle';
72
80
  await buildStaticBundle({
73
81
  entry: 'entry.tsx', // renders <SfaLoginGate>…</SfaLoginGate> into #root
74
82
  outDir: 'dist',
75
- studioVersion: '0.5.0', // the Studio build version, NOT the sfa-kit npm version
76
83
  apiBase: 'https://keystone-api-staging.chikyu.mobi',
77
84
  accessToken: token,
78
85
  });
@@ -84,7 +91,6 @@ Or from the command line, which is the shorter path when a build script drives i
84
91
  SFA_KIT_ACCESS_TOKEN=<token> npx sfa-kit-bundle \
85
92
  --entry entry.tsx \
86
93
  --out dist \
87
- --studio-version 0.5.0 \
88
94
  --api-base https://keystone-api-staging.chikyu.mobi
89
95
  ```
90
96
 
@@ -95,7 +101,9 @@ Then zip the **contents** of `dist/` — the bundle's files have to sit at the z
95
101
  `esbuild` is an optional peer dependency; install it alongside the kit. Its options are fixed: JS, JSX, TS and CSS bundle, but there is no loader for images or fonts, so a page importing a `.png`, `.svg` or a font — including through `url()` in CSS — fails to build. Inline such assets as data URIs, or reference them by absolute URL.
96
102
  Nothing is written when signing fails, so a failed build never leaves an unsigned bundle behind to be uploaded by mistake.
97
103
 
98
- `studioVersion` has no default on purpose. The signature covers `"{studio_version}:{content_hash}"` and Keystone reads the field back separately, so a guessed value produces a valid signature over the wrong version.
104
+ `sfa_kit_version` is filled in automatically with the version of the sfa-kit that ran the build. esbuild bundles whichever copy of sfa-kit the entry module imports, which is normally the same one, but a project with two installed copies can record one version and ship the other.
105
+
106
+ `studioVersion` (`--studio-version`) is deprecated. When given, the bundle is signed in the legacy format and the manifest carries `studio_version`. Omit it.
99
107
 
100
108
  ### Signing exports
101
109
 
@@ -109,7 +117,7 @@ The lower-level pieces `buildStaticBundle` uses, for a caller that builds its ow
109
117
  | `createManifest` | **Yes** |
110
118
  | `createManifestFromDirectory` | **Yes** |
111
119
 
112
- The three key-taking exports exist for the service that holds the key. Calling them from a page build means inventing a key, which Keystone will not accept — and the mistake is invisible at first, because bundle verification ships in `disabled` mode: the upload succeeds, and only starts failing once the mode is switched to `enforced`.
120
+ The three key-taking exports produce the legacy format, signed over `"{studio_version}:{content_hash}"`. They exist for the service that holds the key. Calling them from a page build means inventing a key, which Keystone will not accept — and the mistake is invisible at first, because bundle verification ships in `disabled` mode: the upload succeeds, and only starts failing once the mode is switched to `enforced`.
113
121
 
114
122
  `content_hash` must come from `computeContentHash`, never from hashing files back to back. The algorithm length-prefixes each path and each file's contents before feeding them into one running SHA-256; without those prefixes `"ab" + "c"` and `"a" + "bc"` hash identically, and the result never matches Keystone's own computation.
115
123
 
@@ -128,7 +136,7 @@ Every page uses `SfaLoginGate` as the outermost wrapper. It auto-detects the hos
128
136
 
129
137
  ### Props
130
138
 
131
- - **`apiBase`** (optional) — Overrides the auto-detected Keystone API endpoint. When omitted, the endpoint is resolved from a hostname: the embedding SFA/CRM host's when the page runs inside the iframe, the page's own when it runs standalone. Non-production environments (`stg.chikyu.mobi`, `dev01`-`dev09`, `devdc`, `deva0`, `dev-docker`, `dev.chikyu.local`, `localhost`, `127.0.0.1`, `[::1]`, and every Amplify host of the new frontend under `*.amplify.axiom.chikyu.mobi`: PR previews plus the `devNN.waf.` / `stg.waf.` record-detail hosts) use `https://keystone-api-staging.chikyu.mobi`; everything else (`app.chikyu.net`, any hostname not in that list) uses `https://keystone-api-production.chikyu.mobi`. Production is the fallback on purpose so a page that reaches a customer never silently talks to staging.
139
+ - **`apiBase`** (optional) — Overrides the auto-detected Keystone API endpoint. When omitted, the endpoint is resolved from a hostname: the embedding SFA/CRM host's when the page runs inside the iframe, the page's own when it runs standalone. `dev09` (`dev09.chikyu.mobi` and `dev09.waf.amplify.axiom.chikyu.mobi`) has a Keystone of its own and uses `https://keystone-api-dev09.chikyu.mobi`. The other non-production environments (`stg.chikyu.mobi`, `dev01`-`dev08`, `devdc`, `deva0`, `dev-docker`, `dev.chikyu.local`, `localhost`, `127.0.0.1`, `[::1]`, and every Amplify host of the new frontend under `*.amplify.axiom.chikyu.mobi`: PR previews plus the `devNN.waf.` / `stg.waf.` record-detail hosts) use `https://keystone-api-staging.chikyu.mobi`; everything else (`app.chikyu.net`, any hostname not in that list) uses `https://keystone-api-production.chikyu.mobi`. Production is the fallback on purpose so a page that reaches a customer never silently talks to staging.
132
140
  - **`trustedOrigin`** (optional) — The origin of the SFA/CRM host page (e.g. `"https://app.chikyu.net"`). When provided and the page runs inside an iframe, `SfaLoginGate` uses the postMessage code-exchange flow (`POST /v1/auth/exchange`) instead of a login form. Injected automatically by Keystone's content endpoint at serve time.
133
141
  - **`defaultOrganizationId`** (optional) — The org ID to auto-select when the user's account belongs to multiple organizations. **Set this to the org that generated the page's metadata** (the org used in Step 0 schema discovery). Without this prop, multi-org accounts auto-select the first available org, which may not be the intended one. Use an env var, not a literal: `defaultOrganizationId={Number(process.env.NEXT_PUBLIC_DEFAULT_ORG_ID)}`.
134
142
  - **`objectKey`** (optional) — The object this page is built for (e.g. `"companies"`). The standalone DevToolbar shows it in the page-info tooltip and in the record ID placeholder. Pass it at **both** levels; omit it only if the page genuinely has no object.
@@ -222,6 +230,8 @@ const { record, isLoading, error, refetch } = useRecord('companies', 'abc123');
222
230
 
223
231
  Returns `{ record: SfaRecord | null, isLoading, error, refetch }`.
224
232
 
233
+ The result always describes the `collection` and `recordId` passed right now: from the first render after either changes it reports no record and no error — with `isLoading` while the new fetch runs — so the previous record or error never shows as the answer to the new one. An empty `recordId` with a collection given sets `error` instead of fetching. Disabling the hook (`enabled: false`) without changing them keeps the record already fetched.
234
+
225
235
  ### useRecordList
226
236
 
227
237
  Fetches a paginated list of records. Supports cursor-based pagination (Keystone API).
@@ -715,7 +725,7 @@ The state for `status === 'error'`. Renders `⚠ 取得失敗` and, when given `
715
725
 
716
726
  Props: `{ onRetry?: () => void, message?: string, retryLabel?: string }`. Without `onRetry` no link is rendered — a dead control is worse than none.
717
727
 
718
- The server's error text is deliberately not shown: it is wording the reader cannot act on. Pages that need it still have `error` from the hook, and `RecordList` / `RecordView` report it through their `onError` prop.
728
+ The server's error text is deliberately not shown: it is wording the reader cannot act on. Pages that need it still have `error` from the hook, and `RecordList` / `RecordView` report it through their `onError` prop (`RecordView` does not for an empty record id — see [RecordView](#recordview)).
719
729
 
720
730
  `refetch` retries whichever step failed. On `useRelatedRecords` that includes a failed describe: retrying only the list would do nothing, because without relationship metadata the list never ran.
721
731
 
@@ -814,6 +824,10 @@ Props:
814
824
 
815
825
  Each group: `title?` (heading), `layout?` (`"1-col"` | `"2-col"` | `"3-col"`), `fields` (field names).
816
826
 
827
+ **States.** While the record is fetched it shows "Loading...". A failed fetch shows `FetchErrorState` and calls `onError`. When no record is specified — `collection` or `recordId` is empty — it shows `EmptyState` with "レコードが指定されていません" and does **not** call `onError`: nothing was fetched, so there is no failure to report. `useRecord` on its own still sets `error` for an empty record id (a collection given, no record picked), so a page built on the hook can word that case itself.
828
+
829
+ **Heading.** `title` if given, else the record's own title, else the values of its title fields joined by spaces. Only values a reader understands go into the heading — a reference or choice field without a label is left out rather than showing its id or code.
830
+
817
831
  ### RecordList
818
832
 
819
833
  Displays a paginated table of records.
@@ -834,6 +848,8 @@ Props:
834
848
 
835
849
  `RecordView` takes the same prop and, without it, names its `title` — the label it already shows as the heading — so a page that passed `title="会社"` gets `⚠ 会社の取得に失敗` for free.
836
850
 
851
+ **States.** While the list is fetched it shows "Loading...". A failed fetch shows `FetchErrorState` and calls `onError`. Without a `collection` nothing is asked, so instead of "0 records" it shows `EmptyState` with "オブジェクトが指定されていません".
852
+
837
853
  ### FieldRenderer
838
854
 
839
855
  Renders a single field value based on its kind. Used internally by RecordView and RecordList. Use directly when building custom layouts.
@@ -842,6 +858,10 @@ Renders a single field value based on its kind. Used internally by RecordView an
842
858
  <FieldRenderer fieldValue={someField} fieldName="company_name" />
843
859
  ```
844
860
 
861
+ It shows `label` (formatted by the API). When `label` is empty or `null`, it falls back to `value` only for `text` / `number` / `date` / `datetime` / `computed` fields whose value is a number, a boolean or a string that is not markup; a choice or reference field, whose value is a code or an id, shows `—` instead. The RecordView heading follows the same rule.
862
+
863
+ A single-line `text` value becomes a link when it is a URL, an email address, or a phone number — a Japanese number starting with 0 with 10 or 11 digits (`03-1234-5678`), or an international one starting with `+` with 8 to 15 digits. Postal codes (`100-0001`) and dates (`2026-09-28`) stay plain text.
864
+
845
865
  ## Design tokens
846
866
 
847
867
  All kit components use CSS custom properties with `--sfa-*` prefixes. Use these in your generated UI for visual consistency with SFA/CRM:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geniee-inc/sfa-kit",
3
- "version": "1.17.0",
3
+ "version": "1.19.0",
4
4
  "description": "React hooks and components for building pages that connect to GENIEE SFA/CRM data.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -11,16 +11,15 @@ Usage:
11
11
  ${TOKEN_ENV}=<token> npx sfa-kit-bundle \\
12
12
  --entry entry.tsx \\
13
13
  --out dist \\
14
- --studio-version 0.5.0 \\
15
14
  --api-base https://keystone-api-staging.chikyu.mobi
16
15
 
17
16
  Options:
18
17
  --entry Entry module that mounts the page into #root. Required.
19
18
  --out Output directory. Required.
20
- --studio-version The Studio build version this bundle targets, not the
21
- sfa-kit npm version. Required.
22
19
  --api-base Keystone API base, without a trailing slash. Required.
23
20
  --title <title> of the generated index.html. Default: SFA/CRM.
21
+ --studio-version Deprecated; omit it. When given, the bundle is signed in
22
+ the legacy format, over "{studio_version}:{content_hash}".
24
23
  --help Show this message.
25
24
 
26
25
  Environment:
@@ -28,7 +27,8 @@ Environment:
28
27
  API is called with. Required, and read from the environment rather than an
29
28
  argument so it does not land in shell history or in the process list.
30
29
 
31
- Writes index.html, assets/bundle.js and a signed manifest.json. Zip the
30
+ Writes index.html, assets/bundle.js and a signed manifest.json, which records
31
+ this sfa-kit version as sfa_kit_version. Zip the
32
32
  contents of the output directory — the files have to sit at the zip root.
33
33
  Needs esbuild installed alongside the kit.`;
34
34
  const OPTIONS = {
@@ -70,7 +70,7 @@ function parseCliOptions(argv) {
70
70
  */
71
71
  export function runBundleCli(argv, env, out) {
72
72
  return __awaiter(this, void 0, void 0, function* () {
73
- var _a, _b, _c, _d, _e, _f;
73
+ var _a, _b, _c, _d, _e;
74
74
  const parsed = parseCliOptions(argv);
75
75
  if (parsed.error != null) {
76
76
  out.error(`${parsed.error}\n\n${USAGE}`);
@@ -81,7 +81,7 @@ export function runBundleCli(argv, env, out) {
81
81
  out.log(USAGE);
82
82
  return 0;
83
83
  }
84
- const missing = ['entry', 'out', 'studio-version', 'api-base']
84
+ const missing = ['entry', 'out', 'api-base']
85
85
  .filter(name => { var _a; return ((_a = values[name]) !== null && _a !== void 0 ? _a : '') === ''; })
86
86
  .map(name => `--${name}`);
87
87
  const accessToken = (_a = env[TOKEN_ENV]) !== null && _a !== void 0 ? _a : '';
@@ -91,10 +91,18 @@ export function runBundleCli(argv, env, out) {
91
91
  out.error(`Missing required: ${missing.join(', ')}\n\n${USAGE}`);
92
92
  return 1;
93
93
  }
94
+ // NOTE: an empty value is refused, not read as "omitted". Omitting the flag
95
+ // switches the signing format, and a script whose variable came out empty
96
+ // should hear about it rather than quietly get the other format.
97
+ const studioVersion = values['studio-version'];
98
+ if (studioVersion != null && studioVersion.trim() === '') {
99
+ out.error(`--studio-version must not be empty. Omit it to sign content_hash alone.\n\n${USAGE}`);
100
+ return 1;
101
+ }
94
102
  try {
95
- const manifest = yield buildStaticBundle(Object.assign({ entry: (_b = values.entry) !== null && _b !== void 0 ? _b : '', outDir: (_c = values.out) !== null && _c !== void 0 ? _c : '', studioVersion: (_d = values['studio-version']) !== null && _d !== void 0 ? _d : '', apiBase: (_e = values['api-base']) !== null && _e !== void 0 ? _e : '', accessToken }, (((_f = values.title) !== null && _f !== void 0 ? _f : '') === '' ? {} : { title: values.title })));
103
+ const manifest = yield buildStaticBundle(Object.assign(Object.assign({ entry: (_b = values.entry) !== null && _b !== void 0 ? _b : '', outDir: (_c = values.out) !== null && _c !== void 0 ? _c : '', apiBase: (_d = values['api-base']) !== null && _d !== void 0 ? _d : '', accessToken }, (((_e = values.title) !== null && _e !== void 0 ? _e : '') === '' ? {} : { title: values.title })), (studioVersion == null ? {} : { studioVersion })));
96
104
  out.log(`Built and signed ${values.out} ` +
97
- `(studio_version ${manifest.studio_version}, ` +
105
+ `(sfa_kit_version ${manifest.sfa_kit_version}, ` +
98
106
  `content_hash ${manifest.content_hash}).`);
99
107
  return 0;
100
108
  }
package/src/bundle.d.ts CHANGED
@@ -13,7 +13,6 @@
13
13
  * await buildStaticBundle({
14
14
  * entry: 'entry.tsx',
15
15
  * outDir: 'dist',
16
- * studioVersion: '0.5.0',
17
16
  * apiBase: 'https://keystone-api-staging.chikyu.mobi',
18
17
  * accessToken: token,
19
18
  * });
@@ -21,6 +20,6 @@
21
20
  *
22
21
  * @packageDocumentation
23
22
  */
24
- export type { BundleManifest } from './lib/signing/index.js';
23
+ export type { BundleManifest, ContentHashManifest, } from './lib/signing/index.js';
25
24
  export type { BuildStaticBundleOptions } from './lib/bundle/index.js';
26
25
  export { buildStaticBundle } from './lib/bundle/index.js';
package/src/index.d.ts CHANGED
@@ -34,7 +34,7 @@ export { ErrorBoundary } from './lib/components/ErrorBoundary';
34
34
  export { EmptyState } from './lib/components/EmptyState';
35
35
  export { FetchErrorState } from './lib/components/FetchErrorState';
36
36
  export { ErrorSnackbar } from './lib/components/ErrorSnackbar';
37
- export type { SfaAuth, SfaApiMode, SfaClientConfig, SfaRecord, SfaListResponse, SfaListParams, SfaFieldValue, SfaFieldKind, SfaFieldValueMapping, FieldGroupSpec, FieldGroupLayout, SfaFieldDescriptor, SfaFieldOption, SfaRelationDescriptor, SfaObjectDetail, SfaObjectSummary, SfaObjectCatalog, SfaObjectPermissions, SfaObjectOrigin, SfaRelationshipKind, SfaCardinality, SfaDescribeResult, SfaFieldTypeDescriptor, SfaFieldTypeCatalog, SfaFilterOperator, SfaCreateOptions, SfaFieldError, SfaBulkRowStatus, SfaBulkRowResult, SfaBulkRowError, SfaBulkUpdateRow, SfaBulkDeleteRow, } from './lib/provider/types';
37
+ export type { SfaAuth, SfaApiMode, SfaAuthErrorHandler, SfaClientConfig, SfaRecord, SfaListResponse, SfaListParams, SfaFieldValue, SfaFieldKind, SfaFieldValueMapping, FieldGroupSpec, FieldGroupLayout, SfaFieldDescriptor, SfaFieldOption, SfaRelationDescriptor, SfaObjectDetail, SfaObjectSummary, SfaObjectCatalog, SfaObjectPermissions, SfaObjectOrigin, SfaRelationshipKind, SfaCardinality, SfaDescribeResult, SfaFieldTypeDescriptor, SfaFieldTypeCatalog, SfaFilterOperator, SfaCreateOptions, SfaFieldError, SfaBulkRowStatus, SfaBulkRowResult, SfaBulkRowError, SfaBulkUpdateRow, SfaBulkDeleteRow, } from './lib/provider/types';
38
38
  export { SFA_BULK_MAX_ROWS } from './lib/provider/SfaClient';
39
39
  export type { SfaSession, AuthResult, OrganizationChoice, } from './lib/auth/types';
40
40
  export { buildRecordDetailUrl, resolveHostOrigin, } from './lib/navigation/record-detail-url';
@@ -1,12 +1,14 @@
1
1
  export declare const KEYSTONE_PRODUCTION_API_BASE = "https://keystone-api-production.chikyu.mobi";
2
2
  export declare const KEYSTONE_STAGING_API_BASE = "https://keystone-api-staging.chikyu.mobi";
3
+ export declare const KEYSTONE_DEV09_API_BASE = "https://keystone-api-dev09.chikyu.mobi";
3
4
  /**
4
5
  * @description Resolves the Keystone API base URL from an origin.
5
6
  *
6
- * Every non-production chikyu environment — `stg.chikyu.mobi`, the dev tier,
7
- * local development, and Amplify PR previews — uses the staging Keystone API.
8
- * Everything else — `app.chikyu.net`, an origin this table does not know, or
9
- * no origin at all — uses production.
7
+ * `dev09` has a keystone of its own. Every other non-production chikyu
8
+ * environment — `stg.chikyu.mobi`, the rest of the dev tier, local development,
9
+ * and Amplify PR previews — uses the staging Keystone API. Everything else —
10
+ * `app.chikyu.net`, an origin this table does not know, or no origin at all —
11
+ * uses production.
10
12
  *
11
13
  * Production is the fallback on purpose: a page that reaches a real customer
12
14
  * must never silently talk to staging. A developer on an unrecognised host
@@ -1,6 +1,10 @@
1
1
  export const KEYSTONE_PRODUCTION_API_BASE = 'https://keystone-api-production.chikyu.mobi';
2
2
  export const KEYSTONE_STAGING_API_BASE = 'https://keystone-api-staging.chikyu.mobi';
3
- /** Dev-tier chikyu (legacy frontend) hosts: dev01-dev09, devdc, deva0, and the like. */
3
+ export const KEYSTONE_DEV09_API_BASE = 'https://keystone-api-dev09.chikyu.mobi';
4
+ /**
5
+ * Dev-tier chikyu (legacy frontend) hosts: dev01-dev08, devdc, deva0, and the
6
+ * like. `dev09` matches this too but is taken by `DEV09_HOSTNAMES` first.
7
+ */
4
8
  const DEV_HOST_PATTERN = /^dev\w+\.chikyu\.mobi$/;
5
9
  /**
6
10
  * Amplify-hosted new frontend. Covers PR previews
@@ -21,13 +25,26 @@ const LOCAL_HOSTNAMES = new Set([
21
25
  ]);
22
26
  /** Shared pre-production chikyu environments. */
23
27
  const STAGING_HOSTNAMES = new Set(['stg.chikyu.mobi']);
28
+ /**
29
+ * dev09 has a keystone of its own. One keystone delegates to exactly one chikyu
30
+ * environment, so the staging keystone follows `stg` and this one follows
31
+ * `dev09`, the JapanAI Studio integration environment. Both of dev09's frontends
32
+ * are listed: the legacy chikyu host and the Amplify host that frames the page.
33
+ * Exact hostnames, because `DEV_HOST_PATTERN` and `AMPLIFY_PATTERN` below would
34
+ * otherwise claim them for staging.
35
+ */
36
+ const DEV09_HOSTNAMES = new Set([
37
+ 'dev09.chikyu.mobi',
38
+ 'dev09.waf.amplify.axiom.chikyu.mobi',
39
+ ]);
24
40
  /**
25
41
  * @description Resolves the Keystone API base URL from an origin.
26
42
  *
27
- * Every non-production chikyu environment — `stg.chikyu.mobi`, the dev tier,
28
- * local development, and Amplify PR previews — uses the staging Keystone API.
29
- * Everything else — `app.chikyu.net`, an origin this table does not know, or
30
- * no origin at all — uses production.
43
+ * `dev09` has a keystone of its own. Every other non-production chikyu
44
+ * environment — `stg.chikyu.mobi`, the rest of the dev tier, local development,
45
+ * and Amplify PR previews — uses the staging Keystone API. Everything else —
46
+ * `app.chikyu.net`, an origin this table does not know, or no origin at all —
47
+ * uses production.
31
48
  *
32
49
  * Production is the fallback on purpose: a page that reaches a real customer
33
50
  * must never silently talk to staging. A developer on an unrecognised host
@@ -41,6 +58,8 @@ export function resolveApiBase(origin) {
41
58
  const hostname = hostnameOf(origin);
42
59
  if (hostname == null)
43
60
  return KEYSTONE_PRODUCTION_API_BASE;
61
+ if (DEV09_HOSTNAMES.has(hostname))
62
+ return KEYSTONE_DEV09_API_BASE;
44
63
  const isNonProduction = STAGING_HOSTNAMES.has(hostname) ||
45
64
  LOCAL_HOSTNAMES.has(hostname) ||
46
65
  DEV_HOST_PATTERN.test(hostname) ||
@@ -1,3 +1,4 @@
1
+ import type { SfaAuthErrorHandler } from '../provider/types';
1
2
  import type { EmbedContext } from './embed-protocol';
2
3
  import type { SfaSession } from './types';
3
4
  export type EmbedPhase = {
@@ -28,7 +29,7 @@ export type EmbedPhase = {
28
29
  export interface EmbedSessionState {
29
30
  readonly phase: EmbedPhase;
30
31
  /** Passed to SfaProvider so a 401 triggers the code-request fallback. */
31
- readonly handleAuthError: () => void;
32
+ readonly handleAuthError: SfaAuthErrorHandler;
32
33
  }
33
34
  /**
34
35
  * @description Keeps an embedded page authenticated over the postMessage