-
Notifications
You must be signed in to change notification settings - Fork 7
Module Keyring
src/lib/keyring.ts (137 行)。script タグから API キーを抽出して保持するシングルトン。embed 全体で 1 つのインスタンスを共有する。
class Keyring {
get apiKey(): string;
set apiKey(key: string);
get stage(): string;
set stage(stage: string);
get isGeoloniaStyle(): boolean;
set isGeoloniaStyle(value: boolean);
public parse(doc: Document = document): void;
public reset(): void;
public isGeoloniaStyleCheck(style: string): boolean;
}
const keyring = new Keyring();
export { keyring };class Keyring {
#apiKey: string;
#stage: string;
#isGeoloniaStyle: boolean;
}ECMAScript private field (#) で内部保護。
get apiKey() {
if (!this.#apiKey) {
this.parse();
}
return this.#apiKey;
}
set apiKey(key: string) {
this.#apiKey = key;
}遅延 parse: getter 呼び出し時に #apiKey が未設定なら自動で parse() を実行。
get stage() {
if (!this.#stage) {
this.parse();
}
return this.#stage;
}
set stage(stage: string) {
this.#stage = stage;
}同様に遅延 parse。
get isGeoloniaStyle() {
return this.#isGeoloniaStyle;
}
set isGeoloniaStyle(value: boolean) {
this.#isGeoloniaStyle = value;
}getter は parse を起動しない。明示的にセットされていない場合は undefined を返す。
セットは parse-atts.ts:31 で行う。
script タグから API キーを抽出する唯一の方法。
const scripts: HTMLScriptElement[] | HTMLCollectionOf<HTMLScriptElement> =
doc.currentScript
? [doc.currentScript as HTMLScriptElement]
: doc.getElementsByTagName('script');-
document.currentScriptがあればそれだけを対象 (= 現在実行中の script) - なければ全
<script>タグを走査
for (const script of scripts) {
const url = new URL(
script.src.startsWith('https://') ||
script.src.startsWith('http://') ||
script.src.startsWith('//')
? script.src
: `https://${location.host}/${script.src}`,
);
const apiKey = url.searchParams.get('geolonia-api-key');
if (apiKey) {
this.#apiKey = apiKey;
this.#stage = process.env.MAP_PLATFORM_STAGE || 'dev';
break;
}
}- script の
srcがhttps://,http://,//で始まらなければhttps://{host}/{src}の絶対 URL に展開 - URL の
?geolonia-api-key=パラメータが API キー - 最初に見つかった script で break (複数 script が embed.js を読んでいても 1 つ目だけ採用)
if (!this.#stage) {
this.#stage = 'dev';
}
if (this.#isGeoloniaStyle && !this.#apiKey) {
throw new Error('Cannot load API key.');
}- API キーが見つからなくても stage は
'dev'で fallback - ただし
#isGeoloniaStyle === trueかつ#apiKey未取得ならここで throw
注: process.env.MAP_PLATFORM_STAGE は webpack DefinePlugin でビルド時に静的置換される (webpack.config.js:7)。npm scripts (package.json:18,21,22) は MAP_PLATFORM_STAGE=v1 を渡しているので、npm start や npm run build でビルドしたバンドルは 'v1' 固定。それ以外 (例えば直接 webpack コマンド) なら 'dev'。
#apiKey がすでにセットされていても、再度呼ぶと再パースして上書きする。getter 経由の遅延 parse は !#apiKey ガードがあるので 1 回だけ。
しかし render.ts:21 と parse-atts.ts:27 で毎回呼ばれるので、実際には毎マップ初期化で再パースが走る (script タグの内容が変わらなければ結果同じだが、冗長)。
public reset() {
this.#apiKey = undefined;
this.#stage = undefined;
this.#isGeoloniaStyle = false;
}テスト用。プロダクションコードからは呼ばない。keyring.test.ts で各テスト前に呼んでいる。
スタイル文字列が Geolonia 内部スタイル (= API キー必須) かどうかを判定。
public isGeoloniaStyleCheck(style: string): boolean {
if (!style || style === '') {
return true; // デフォルトは Geolonia
}
if (style.startsWith('https://cdn.geolonia.com/style/') ||
style.startsWith('https://api.geolonia.com/')) {
return true;
}
if (style.match(/^https?:\/\//)) {
return false; // 外部 URL
}
if (style.endsWith('.json')) {
try {
const absoluteUrl = new URL(style, location.href).href;
return absoluteUrl.includes('geolonia.com');
} catch {
return false;
}
}
return true; // 論理名扱い (例: 'geolonia/basic')
}- 空文字列 → Geolonia (デフォルトスタイル想定)
-
https://cdn.geolonia.com/style/またはhttps://api.geolonia.com/で始まる → Geolonia -
http(s)://で始まる (上記 2 以外) → 外部 -
.jsonで終わる (絶対 URL ではない、つまり相対パス) → resolve してgeolonia.comを含むか確認 - 上記すべて該当しない → Geolonia 論理名 (例:
'geolonia/basic','foo/bar')
-
'./styles/foo.json'→ 相対パス。new URL('./styles/foo.json', location.href)でlocation.host由来の URL になる。location.hostがgeolonia.comを含むなら Geolonia 判定、それ以外なら外部 -
'/style.json'→ 同じく相対 (絶対パス)。location.host依存 -
''(空文字列) → Geolonia (1 で early return) -
'foo'(single token) → Geolonia (5 にフォールスルー) -
'foo/bar/baz'→ Geolonia (URL とみなさない) -
'geolonia.com/foo'(スキームなし) → Geolonia (5 にフォールスルー、URL チェックはスキーム必須)
シングルトン (keyring.ts:134):
const keyring = new Keyring(); // Singleton
export { keyring };- モジュールロード時に 1 つだけ生成される
- import するすべての場所で同じインスタンスを共有
- ページのライフタイム全期間で持続
- ページリロードでリセット
Web Worker 等の別 realm では別インスタンスになる。embed コードは Web Worker で動作することを想定していないので問題ないが、書き直し時に Worker 対応するなら考慮要。
| 呼び出し元 | 用途 |
|---|---|
render.ts:21 |
renderGeoloniaMap 開始時に keyring.parse()
|
parse-atts.ts:27 |
parseAtts 内で keyring.parse(container) (container 引数は誤用、Module-Parse-Atts 参照) |
parse-atts.ts:31 |
keyring.isGeoloniaStyle = keyring.isGeoloniaStyleCheck(style) |
parse-atts.ts:58,59 |
keyring.apiKey, keyring.stage getter |
util.ts:59 |
checkPermission 内で keyring.apiKey チェック |
util.ts:219 |
getStyle 内で keyring.isGeoloniaStyleCheck(style) 呼び出し |
render.ts:90 |
keyring.isGeoloniaStyle && !keyring.apiKey 警告判定 |
-
API キー必須シーンで未取得:
-
parse()内で#isGeoloniaStyle === true && !#apiKey→throw new Error('Cannot load API key.')(keyring.ts:84) - stack trace が parse 呼び出し元になるので、原因 (どの map / どの DOM 要素か) が特定しにくい
-
-
isGeoloniaStyleが未設定で apiKey 必須:-
parse()の throw を回避するため、parse-atts.ts:31のisGeoloniaStyleセットが必須 - parse-atts より先に
keyring.apiKeyを直接読むコードがあると意図しない state で parse される
-
-
isGeoloniaStyleCheckの 5-way 分岐の境界条件 -
parse-atts.ts:27のkeyring.parse(container)引数誤用 - throw 時の stack trace 不明瞭性
- stage の build-time 固定
- maps-core (lib) 側に置く
- ただし
parse()が DOM を走査するので、lib として "pure" にするならparse(scripts: HTMLScriptElement[])のように引数で受ける形に - 自動 parse は wrapper 側で
keyring.parse(document.getElementsByTagName('script'))を呼ぶ -
MAP_PLATFORM_STAGEの build-time 注入はランタイム可変化を検討 (lib の汎用性のため) -
isGeoloniaStyleCheckのロジックは utility 関数として独立化