(function (root, factory) { var api = factory(); if (typeof module === 'object' && module.exports) { module.exports = api; } if (root) { root.TaberebaLinkHistory = api; } }(typeof window !== 'undefined' ? window : globalThis, function () { 'use strict'; var storageKey = 'tabereba_history_v1'; var maxItems = 20; function text(value, limit) { return String(value === null || typeof value === 'undefined' ? '' : value) .replace(/[\u0000-\u001f\u007f]/g, '').trim().slice(0, limit); } function safeHttpsUrl(value, hosts) { try { var url = new URL(text(value, 2048)); return url.protocol === 'https:' && url.username === '' && url.password === '' && url.port === '' && hosts.indexOf(url.hostname.toLowerCase()) !== -1 ? url.href : ''; } catch (error) { return ''; } } function coordinate(value, minimum, maximum) { if (value === '' || value === null || typeof value === 'undefined') { return null; } var number = Number(value); return isFinite(number) && number >= minimum && number <= maximum ? number : null; } function normalizedDate(value, fallback) { var date = value instanceof Date ? value : new Date(value); if (!isNaN(date.getTime())) { return date; } return fallback instanceof Date && !isNaN(fallback.getTime()) ? fallback : new Date(); } function normalizeItem(value, now) { value = value && typeof value === 'object' ? value : {}; now = normalizedDate(now, new Date()); var shopId = text(value.shop_id, 10).toUpperCase(); shopId = /^J[0-9]{9}$/.test(shopId) ? shopId : ''; var name = text(value.name, 200); return { id: shopId ? 'hotpepper|' + shopId : '', source: 'hotpepper_gourmet', shop_id: shopId, name: name, search_keyword: text(value.search_keyword || name, 100), ordinary_url: safeHttpsUrl(value.ordinary_url, ['www.hotpepper.jp']), image: safeHttpsUrl(value.image, ['imgfp.hotp.jp']), image_large: safeHttpsUrl(value.image_large || value.image, ['imgfp.hotp.jp']), image_medium: safeHttpsUrl(value.image_medium || value.image, ['imgfp.hotp.jp']), image_small: safeHttpsUrl(value.image_small || value.image, ['imgfp.hotp.jp']), address: text(value.address, 300), station_name: text(value.station_name, 100), access: text(value.access, 300), latitude: coordinate(value.latitude, -90, 90), longitude: coordinate(value.longitude, -180, 180), genre_name: text(value.genre_name, 100), catch_text: text(value.catch_text, 300), site_name_at_generation: text(value.site_name_at_generation, 100), generated_at: now.toISOString() }; } function usable(item) { return item.shop_id !== '' && item.name !== '' && item.search_keyword !== '' && item.ordinary_url !== ''; } function emptyHistory() { return {service: 'tabereba', schema_version: 1, version: 1, updated_at: '', items: []}; } function normalizeHistory(value) { var result = emptyHistory(); if (!value || typeof value !== 'object' || value.service !== 'tabereba' || value.schema_version !== 1 || value.version !== 1 || !Array.isArray(value.items)) { return result; } value.items.forEach(function (raw) { var item = normalizeItem(raw, normalizedDate(raw && raw.generated_at, new Date())); if (usable(item) && !result.items.some(function (old) { return old.id === item.id; })) { result.items.push(item); } }); result.items = result.items.slice(0, maxItems); result.updated_at = text(value.updated_at, 40); return result; } function upsert(history, value, now) { var item = normalizeItem(value, now); var result = normalizeHistory(history); if (!usable(item)) { return result; } result.items = [item].concat(result.items.filter(function (old) { return old.id !== item.id; })).slice(0, maxItems); result.updated_at = item.generated_at; return result; } function createStore(storage) { function load() { try { var raw = storage.getItem(storageKey); return raw ? normalizeHistory(JSON.parse(raw)) : emptyHistory(); } catch (error) { return emptyHistory(); } } function save(history) { var normalized = normalizeHistory(history); try { storage.setItem(storageKey, JSON.stringify(normalized)); return {success: true, history: normalized}; } catch (error) { return {success: false, history: normalized}; } } return { load: load, upsert: function (item, now) { return save(upsert(load(), item, now)); }, remove: function (id) { var history = load(); history.items = history.items.filter(function (item) { return item.id !== id; }); return save(history); }, clear: function () { try { storage.removeItem(storageKey); return {success: true, history: emptyHistory()}; } catch (error) { return {success: false, history: load()}; } } }; } return {storageKey: storageKey, maxItems: maxItems, emptyHistory: emptyHistory, normalizeItem: normalizeItem, normalizeHistory: normalizeHistory, upsert: upsert, createStore: createStore}; }));