@linkitbe/sdk 1.0.2 → 1.0.8

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.
@@ -20,7 +20,9 @@
20
20
  enableFeedAutoProcess: false,
21
21
  // Корень наблюдения для фида (обязателен при enableFeedAutoProcess=true).
22
22
  feedObserverRootSelector: null,
23
- sdkVersion: '1.0.2',
23
+ // true = перед POST integration-viewable запросить GET /v1/events/integration-viewable-token (нужен секрет на markup-api).
24
+ fetchImpressionTokenForViewable: true,
25
+ sdkVersion: '1.0.8',
24
26
  styles: {
25
27
  link_color: '#e2187b',
26
28
  link_weight: '700',
@@ -113,6 +115,32 @@
113
115
  return null;
114
116
  }
115
117
 
118
+ async function fetchIntegrationViewableImpressionToken(articleId, processingId) {
119
+ const partnerKey = config.partnerApiKey ? String(config.partnerApiKey).trim() : '';
120
+ if (!partnerKey || !processingId) return null;
121
+ try {
122
+ const base = String(config.apiBaseUrl || 'https://api.linkitbe.com').replace(/\/$/, '');
123
+ const u = new URL(base + '/v1/events/integration-viewable-token');
124
+ u.searchParams.set('article_id', articleId);
125
+ u.searchParams.set('domain', String(config.domain || ''));
126
+ u.searchParams.set('processing_id', String(processingId));
127
+ const res = await fetch(u.toString(), {
128
+ method: 'GET',
129
+ headers: {
130
+ 'Accept': 'application/json',
131
+ 'X-Partner-Api-Key': partnerKey,
132
+ },
133
+ credentials: 'omit',
134
+ });
135
+ if (!res.ok) return null;
136
+ const j = await res.json();
137
+ return j && j.impression_token ? String(j.impression_token) : null;
138
+ } catch (e) {
139
+ console.warn('[LinkItBe] impression token fetch failed', e);
140
+ return null;
141
+ }
142
+ }
143
+
116
144
  async function emitSdkEvent(eventType, path, articleId, integration, extra) {
117
145
  const processingId = extractProcessingIdFromIntegrations([integration]);
118
146
  if (!processingId) return;
@@ -136,6 +164,11 @@
136
164
  referrer: document.referrer || null,
137
165
  }, extra || {});
138
166
 
167
+ if (eventType === 'integration_viewable' && config.fetchImpressionTokenForViewable) {
168
+ const tok = await fetchIntegrationViewableImpressionToken(articleId, processingId);
169
+ if (tok) payload.impression_token = tok;
170
+ }
171
+
139
172
  try {
140
173
  const base = String(config.apiBaseUrl || 'https://api.linkitbe.com').replace(/\/$/, '');
141
174
  await fetch(`${base}${path}`, {
@@ -163,22 +196,52 @@
163
196
 
164
197
  function attachIntegrationViewableObserver(articleId, contentBlock) {
165
198
  if (!articleId) return;
199
+ const clearTimer = (linkEl) => {
200
+ const t = linkEl._lbViewDwellTimer;
201
+ if (t) {
202
+ clearTimeout(t);
203
+ linkEl._lbViewDwellTimer = null;
204
+ }
205
+ };
166
206
  const io = new IntersectionObserver((entries) => {
167
207
  for (let i = 0; i < entries.length; i++) {
168
208
  const en = entries[i];
169
- if (!en.isIntersecting || en.intersectionRatio < 0.5) continue;
170
209
  const linkEl = en.target;
171
210
  if (linkEl.dataset.lbViewableSent === '1') continue;
172
- let integration;
173
- try {
174
- integration = JSON.parse(decodeURIComponent(linkEl.dataset.lbIntegration || ''));
175
- } catch (e) {
176
- io.unobserve(linkEl);
211
+ if (!en.isIntersecting || en.intersectionRatio < 0.5) {
212
+ clearTimer(linkEl);
213
+ linkEl._lbViewVisibleSince = null;
214
+ linkEl._lbViewLastIr = null;
177
215
  continue;
178
216
  }
179
- linkEl.dataset.lbViewableSent = '1';
180
- emitSdkEvent('integration_viewable', '/v1/events/integration-viewable', articleId, integration);
181
- io.unobserve(linkEl);
217
+ linkEl._lbViewLastIr = String(Math.min(1, Math.max(0, en.intersectionRatio)));
218
+ if (!linkEl._lbViewVisibleSince) {
219
+ linkEl._lbViewVisibleSince = Date.now();
220
+ }
221
+ clearTimer(linkEl);
222
+ linkEl._lbViewDwellTimer = setTimeout(() => {
223
+ linkEl._lbViewDwellTimer = null;
224
+ if (linkEl.dataset.lbViewableSent === '1') return;
225
+ if (document.visibilityState !== 'visible') return;
226
+ let integration;
227
+ try {
228
+ integration = JSON.parse(decodeURIComponent(linkEl.dataset.lbIntegration || ''));
229
+ } catch (e) {
230
+ io.unobserve(linkEl);
231
+ return;
232
+ }
233
+ linkEl.dataset.lbViewableSent = '1';
234
+ const start = linkEl._lbViewVisibleSince || Date.now();
235
+ const dwell = Math.max(0, Date.now() - start);
236
+ const ir = parseFloat(linkEl._lbViewLastIr || '0.5');
237
+ emitSdkEvent('integration_viewable', '/v1/events/integration-viewable', articleId, integration, {
238
+ tab_visible: document.visibilityState === 'visible',
239
+ dwell_ms: Math.max(1000, dwell),
240
+ intersection_ratio: ir,
241
+ viewability_rule_version: 'elitesdk-1',
242
+ });
243
+ io.unobserve(linkEl);
244
+ }, 1000);
182
245
  }
183
246
  }, { threshold: [0.5] });
184
247
  contentBlock.querySelectorAll('.__lb_link').forEach((linkEl) => io.observe(linkEl));
@@ -214,6 +277,29 @@
214
277
  return preview;
215
278
  }
216
279
 
280
+ function positionMarkupPreview(preview, clientX, clientY) {
281
+ const margin = 12;
282
+ const offsetX = 16;
283
+ const cursorAnchor = 60;
284
+
285
+ const previewHeight = preview.offsetHeight || 0;
286
+ const previewWidth = preview.offsetWidth || 0;
287
+
288
+ let left = clientX + offsetX;
289
+ let top = clientY - previewHeight + cursorAnchor;
290
+
291
+ const maxLeft = window.innerWidth - previewWidth - margin;
292
+ left = Math.min(Math.max(left, margin), Math.max(margin, maxLeft));
293
+
294
+ const viewportBottom = window.innerHeight - margin;
295
+ if (top + previewHeight > viewportBottom) {
296
+ top = clientY - previewHeight - margin;
297
+ }
298
+
299
+ preview.style.left = left + 'px';
300
+ preview.style.top = Math.max(margin, top) + 'px';
301
+ }
302
+
217
303
  function showPreview(preview, integration, x, y, styles_config) {
218
304
  if (!integration.preview || !integration.preview.img) {
219
305
  preview.style.display = 'none';
@@ -242,9 +328,8 @@
242
328
  cta.textContent = integration.preview.cta ? String(integration.preview.cta) : '';
243
329
  preview.appendChild(cta);
244
330
 
245
- preview.style.left = (x + 16) + 'px';
246
- preview.style.top = (y - 60) + 'px';
247
331
  preview.style.display = 'block';
332
+ positionMarkupPreview(preview, x, y);
248
333
  }
249
334
 
250
335
 
@@ -404,6 +489,113 @@
404
489
  }
405
490
  }
406
491
 
492
+ function normalizePlainMarkerText(markerText) {
493
+ const normalized = String(markerText || '#реклама').trim();
494
+ if (!normalized) {
495
+ return '#реклама';
496
+ }
497
+ if (normalized.endsWith(':')) {
498
+ return normalized;
499
+ }
500
+ return normalized.replace(/:+$/, '') + ':';
501
+ }
502
+
503
+ function appendEridToAdvertiserInfoUrl(rawUrl, erid) {
504
+ const sanitizedErid = erid != null ? String(erid).trim() : '';
505
+ if (!sanitizedErid || !rawUrl) {
506
+ return rawUrl;
507
+ }
508
+
509
+ try {
510
+ const url = new URL(String(rawUrl), window.location.origin);
511
+ url.searchParams.set('erid', sanitizedErid);
512
+ return url.toString();
513
+ } catch (e) {
514
+ return rawUrl;
515
+ }
516
+ }
517
+
518
+ function resolveAdvertiserInfoUrl(integration, pageConfig) {
519
+ const erid = integration && integration.erid != null
520
+ ? String(integration.erid).trim()
521
+ : '';
522
+
523
+ const fromIntegration = sanitizeIntegrationLink(integration && integration.advertiser_info_url);
524
+ if (fromIntegration) {
525
+ return appendEridToAdvertiserInfoUrl(fromIntegration, erid);
526
+ }
527
+
528
+ const base = pageConfig && pageConfig.advertiser_info_base_url;
529
+ const advertiser = integration && integration.advertiser != null
530
+ ? String(integration.advertiser).trim()
531
+ : '';
532
+ if (!base || !advertiser) {
533
+ return null;
534
+ }
535
+
536
+ try {
537
+ const url = new URL(String(base), window.location.origin);
538
+ url.searchParams.append('items[]', advertiser);
539
+ if (erid) {
540
+ url.searchParams.set('erid', erid);
541
+ }
542
+ return url.toString();
543
+ } catch (e) {
544
+ return null;
545
+ }
546
+ }
547
+
548
+ function createAdvertiserInfoLinkElement(integration, linkText, linkStyle, pageConfig) {
549
+ const sanitizedLink = resolveAdvertiserInfoUrl(integration, pageConfig);
550
+ if (!sanitizedLink) {
551
+ console.warn('[LinkItBe] advertiser info url is missing for marker', integration && integration.advertiser);
552
+ return null;
553
+ }
554
+
555
+ const a = document.createElement('a');
556
+ a.href = sanitizedLink;
557
+ a.target = '_blank';
558
+ a.rel = 'noopener noreferrer sponsored nofollow';
559
+ a.className = '__lb_link __lb_advertiser_info_link';
560
+ a.style.cssText = linkStyle.replace(/\s+/g, ' ').trim();
561
+ a.textContent = linkText;
562
+ return a;
563
+ }
564
+
565
+ function buildLabelingMarkerNode(labelingTemplate, integration, pageConfig) {
566
+ const template = labelingTemplate && typeof labelingTemplate === 'object' ? labelingTemplate : {};
567
+ const markerType = template.marker_type || 'plain_text';
568
+ const position = template.position || 'before_phrase';
569
+ const markerText = template.marker_text != null ? String(template.marker_text).trim() : '';
570
+ const labelStyle = 'font-size:11px;color:#999;display:block;margin-bottom:4px;';
571
+ const markerLinkStyle = 'font-size:11px;color:#999;font-weight:400;text-decoration:underline;cursor:pointer;';
572
+
573
+ if (markerType === 'marker_link') {
574
+ const text = markerText || '#реклама';
575
+ const linkEl = createAdvertiserInfoLinkElement(integration, text, markerLinkStyle, pageConfig);
576
+ if (linkEl) {
577
+ const wrapper = document.createElement('span');
578
+ wrapper.style.cssText = labelStyle.replace('margin-bottom:4px;', '');
579
+ wrapper.appendChild(linkEl);
580
+ return { node: wrapper, position: position };
581
+ }
582
+ console.warn('[LinkItBe] marker_link fallback to plain text:', text);
583
+ const span = document.createElement('span');
584
+ span.style.cssText = labelStyle;
585
+ span.textContent = text;
586
+ return { node: span, position: position };
587
+ }
588
+
589
+ if (markerType !== 'plain_text') {
590
+ console.warn('[LinkItBe] unsupported marker_type, fallback to plain_text:', markerType);
591
+ }
592
+
593
+ const span = document.createElement('span');
594
+ span.style.cssText = labelStyle;
595
+ span.textContent = normalizePlainMarkerText(markerText || '#реклама');
596
+ return { node: span, position: position };
597
+ }
598
+
407
599
  function createLinkElement(integration, linkText, linkStyle) {
408
600
  const sanitizedLink = sanitizeIntegrationLink(integration.link);
409
601
  if (!sanitizedLink) {
@@ -466,23 +658,17 @@
466
658
 
467
659
  const MINI_OFFER_PLACEHOLDER = '{anchor_text}';
468
660
 
469
- function applyMiniOffer(contentBlock, integration, linkStyle) {
470
- const sel = config.plainBlockSelector || 'p, blockquote, h1, h2, h3, h4, h5, h6';
471
- const blocks = contentBlock.querySelectorAll(sel);
472
- if (!blocks.length) {
473
- console.warn('[LinkItBe] mini-offer: no content blocks');
661
+ function applyMiniOffer(contentBlock, integration, linkStyle, labelingTemplate, pageConfig) {
662
+ if (!contentBlock) {
663
+ console.warn('[LinkItBe] mini-offer: no content block');
474
664
  return false;
475
665
  }
476
666
 
477
- const lastBlock = blocks[blocks.length - 1];
478
667
  const outro = document.createElement('p');
479
668
  outro.className = '__lb_mini_offer';
480
669
  outro.style.cssText = 'margin-top:16px;font-size:15px;font-family:Manrope, sans-serif;color:#343b4c;';
481
670
 
482
- const adLabel = document.createElement('span');
483
- adLabel.style.cssText = 'font-size:11px;color:#999;display:block;margin-bottom:4px;';
484
- adLabel.textContent = '#реклама';
485
- outro.appendChild(adLabel);
671
+ const marker = buildLabelingMarkerNode(labelingTemplate, integration, pageConfig);
486
672
 
487
673
  const body = document.createElement('span');
488
674
  const template = integration.text != null ? String(integration.text) : '';
@@ -500,12 +686,20 @@
500
686
  body.appendChild(anchor);
501
687
  if (after) body.appendChild(document.createTextNode(after));
502
688
 
689
+ if (marker && marker.position === 'before_phrase') {
690
+ outro.appendChild(marker.node);
691
+ }
503
692
  outro.appendChild(body);
504
- lastBlock.after(outro);
693
+ if (marker && marker.position === 'after_phrase') {
694
+ outro.appendChild(document.createElement('br'));
695
+ outro.appendChild(marker.node);
696
+ }
697
+
698
+ contentBlock.appendChild(outro);
505
699
  return true;
506
700
  }
507
701
 
508
- function applyIntegrations(articleId, integrations, contentBlock, style_config) {
702
+ function applyIntegrations(articleId, integrations, contentBlock, style_config, labeling_template, page_config) {
509
703
  if (!contentBlock) {
510
704
  console.warn('[LinkItBe] Content block not provided');
511
705
  const active = filterIntegrationsByAllowedFormats(integrations);
@@ -545,7 +739,7 @@
545
739
  }
546
740
 
547
741
  active.filter(i => i.type === 'mini-offer').forEach(integ => {
548
- if (applyMiniOffer(contentBlock, integ, linkStyle)) {
742
+ if (applyMiniOffer(contentBlock, integ, linkStyle, labeling_template, page_config)) {
549
743
  appliedCount += 1;
550
744
  } else {
551
745
  console.warn('[LinkItBe] mini-offer failed');
@@ -571,8 +765,7 @@
571
765
  emitSdkEvent('preview_viewed', '/v1/events/preview-viewed', articleId, integration);
572
766
  });
573
767
  linkEl.addEventListener('mousemove', (e) => {
574
- preview.style.left = (e.clientX + 16) + 'px';
575
- preview.style.top = (e.clientY - 60) + 'px';
768
+ positionMarkupPreview(preview, e.clientX, e.clientY);
576
769
  });
577
770
  linkEl.addEventListener('mouseleave', () => { preview.style.display = 'none'; });
578
771
  });
@@ -594,8 +787,10 @@
594
787
 
595
788
  const integrations = Array.isArray(data.integrations) ? data.integrations : [];
596
789
 
597
- const style_config = data.config.style;
598
- const result = applyIntegrations(articleId, integrations, contentBlock, style_config);
790
+ const page_config = data.config && typeof data.config === 'object' ? data.config : {};
791
+ const style_config = page_config.style || {};
792
+ const labeling_template = page_config.labeling_template || null;
793
+ const result = applyIntegrations(articleId, integrations, contentBlock, style_config, labeling_template, page_config);
599
794
 
600
795
  await emitViewRendered(articleId, result.integrations, result.appliedCount);
601
796
  }
@@ -1 +1 @@
1
- !function(){const e=Object.assign({domain:window.location.hostname,bootOnLoad:!0,contentSelector:null,showPreview:!0,plainBlockJoiner:"\n",plainBlockSelector:"p, blockquote, h1, h2, h3, h4, h5, h6",allowedFormats:["keyword","mini-offer"],apiBaseUrl:"https://api.linkitbe.com",partnerApiKey:null,articleIdResolver:null,enableFeedAutoProcess:!1,feedObserverRootSelector:null,sdkVersion:"1.0.2",styles:{link_color:"#e2187b",link_weight:"700",link_decoration:"underline"}},window.LinkItBeConfig||{});function n(n){if("function"==typeof e.articleIdResolver)try{const t=e.articleIdResolver(n),o=null!=t?String(t).trim():"";if(o)return o}catch(e){console.warn("[LinkItBe] articleIdResolver failed",e)}return null}function t(){const e="__lb_session_id";try{const n=window.localStorage.getItem(e);if(n&&n.trim())return n.trim();const t=window.crypto&&window.crypto.randomUUID?window.crypto.randomUUID():`${Date.now()}-${Math.random().toString(16).slice(2)}`;return window.localStorage.setItem(e,t),t}catch(e){return`${Date.now()}-${Math.random().toString(16).slice(2)}`}}function o(e){if(!Array.isArray(e))return null;for(const n of e){const e=n&&n.link?String(n.link):"";if(e)try{const n=new URL(e,window.location.origin).searchParams.get("processing_id"),t=Number(n);if(Number.isFinite(t)&&t>0)return t}catch(e){}}return null}async function r(n,r,i,l,s){const c=o([l]);if(!c)return;const a=e.partnerApiKey?String(e.partnerApiKey).trim():"";if(!a)return;const d=Object.assign({event_id:window.crypto&&window.crypto.randomUUID?window.crypto.randomUUID():`${Date.now()}-${Math.random().toString(16).slice(2)}`,event_type:n,event_time:(new Date).toISOString(),article_id:i,domain:e.domain,processing_id:c,placement_link:window.location.href,integration_type:l&&null!=l.type?l.type:null,sdk_version:String(e.sdkVersion||""),session_id:t(),referrer:document.referrer||null},s||{});try{const n=String(e.apiBaseUrl||"https://api.linkitbe.com").replace(/\/$/,"");await fetch(`${n}${r}`,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json","X-Partner-Api-Key":a},body:JSON.stringify(d),keepalive:!0,credentials:"omit"})}catch(e){console.warn("[LinkItBe] failed to send "+n,e)}}async function i(e,n,t){if(t<=0)return;const i=function(e){if(!Array.isArray(e))return null;for(let n=0;n<e.length;n++){const t=e[n];if(t&&o([t]))return t}return null}(n);i&&await r("view_rendered","/v1/events/view-rendered",e,i,{integrations_count:t})}function l(n){const t=e.allowedFormats;return null!=t&&Array.isArray(t)?n.filter(e=>t.includes(e.type)):n}function s(e){const n=[],t=document.createTreeWalker(e,NodeFilter.SHOW_TEXT,{acceptNode:e=>function(e){const n=e.nodeType===Node.TEXT_NODE?e.parentElement:e;return!!(n&&n.closest&&n.closest("a.__lb_link"))}(e)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT});let o;for(;o=t.nextNode();)n.push(o);return n}function c(e,n){if(!n)return[];const t=new RegExp(String(n).replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),"gi");const o=[];let r;for(;null!==(r=t.exec(e));)o.push({start:r.index,end:r.index+r[0].length}),0===r[0].length&&t.lastIndex===r.index&&t.lastIndex++;return o}function a(e,n,t){const o=function(e){if(null==e)return null;try{const n=new URL(String(e),window.location.origin);return"http:"!==n.protocol&&"https:"!==n.protocol?null:n.toString()}catch(e){return null}}(e.link);if(!o)return null;const r=document.createElement("a");return r.href=o,r.target="_blank",r.rel="noopener noreferrer sponsored nofollow",r.className="__lb_link",r.setAttribute("data-lb-integration",encodeURIComponent(JSON.stringify(e))),r.style.cssText=t.replace(/\s+/g," ").trim(),r.textContent=n,r}function d(n,t){const{plain:o,spans:r}=function(n){const t=null!=e.plainBlockJoiner?e.plainBlockJoiner:"\n",o=e.plainBlockSelector||"p, blockquote, h1, h2, h3, h4, h5, h6",r=n.querySelectorAll(o),i=[];let l="";return r.forEach((e,n)=>{n>0&&(l+=t);const o=s(e);for(const e of o){const n=e.textContent;if(!n.length)continue;const t=l.length;l+=n,i.push({g0:t,g1:l.length,node:e})}}),{plain:l,spans:i}}(n),i=function(e,n){if(!e.length)return null;if(1===e.length)return e[0];const t=n||{},o=t.match_index;if(null!=o&&Number.isFinite(Number(o))){const n=Number(o);if(n>=0&&n<e.length)return e[n];console.warn("[LinkItBe] match_index out of range:",o,"n=",e.length)}const r=t.offset_start,i=t.offset_end;if(null!=r&&Number.isFinite(Number(r))){const n=Number(r),t=null!=i&&Number.isFinite(Number(i))?Number(i):null;let o=e[0],l=1/0;for(const r of e){const e=1e9*Math.abs(r.start-n)+(null!=t?Math.abs(r.end-t):0);e<l&&(l=e,o=r)}return o}return console.warn("[LinkItBe] ambiguous matches, skipping (no match_index / offset_start):",e.length),null}(c(o,t.keyword),t.position);if(!i)return null;const l=o.slice(i.start,i.end);return{plain:o,spans:r,span:i,linkText:l}}function u(e,n,t){const o=d(e,n);if(!o)return console.warn("[LinkItBe] no span for keyword:",n.keyword),!1;const{plain:r,spans:i,span:l,linkText:s}=o,c=function(e,n,t){if(e<0||e>=t||!n.length)return null;const o=n.find(n=>e>=n.g0&&e<n.g1);return o?{node:o.node,offset:e-o.g0}:null}(l.start,i,r.length),u=function(e,n,t){if(!n.length)return null;if(e<=0)return{node:n[0].node,offset:0};if(e>=t){const e=n[n.length-1];return{node:e.node,offset:e.node.textContent.length}}for(let t=0;t<n.length;t++){const o=n[t];if(e>o.g0&&e<=o.g1)return{node:o.node,offset:e-o.g0};if(e===o.g0)return{node:o.node,offset:0}}for(let t=0;t<n.length;t++)if(e<n[t].g0)return{node:n[t].node,offset:0};return null}(l.end,i,r.length);if(!c||!u)return console.warn("[LinkItBe] could not map span to DOM"),!1;const p=r.slice(l.start,l.end);if(p.toLowerCase()!==s.toLowerCase())return console.warn("[LinkItBe] plain slice mismatch"),!1;const f=document.createRange();f.setStart(c.node,c.offset),f.setEnd(u.node,u.offset);const g=a(n,p,t);return g?(function(e,n){try{e.surroundContents(n)}catch(t){const o=e.extractContents();for(;o.firstChild;)n.appendChild(o.firstChild);e.insertNode(n)}}(f,g),!0):(console.warn("[LinkItBe] invalid integration link:",n.link),!1)}const p="{anchor_text}";function f(n,t,o,i){if(!o){console.warn("[LinkItBe] Content block not provided");return{appliedCount:0,integrations:l(t)}}const s=e.showPreview?function(){const e=document.createElement("div");return e.id="__lb_preview",e.style.cssText="\n position: fixed;\n display: none;\n background: white;\n border-radius: 12px;\n box-shadow: 0 4px 20px rgba(0,0,0,0.15);\n padding: 12px;\n z-index: 99999;\n width: 180px;\n font-family: Manrope, sans-serif;\n pointer-events: none;\n ",document.body.appendChild(e),e}():null,c=`\n color:${i.link_color};\n font-weight:${i.link_weight};\n text-decoration:${i.link_decoration};\n cursor:pointer;\n `,f=l(t);let g=0,h=f.filter(e=>"keyword"===e.type);for(;h.length;){const e=[];for(const n of h){const t=d(o,n);t&&e.push({integ:n,spanStart:t.span.start})}if(!e.length){h.forEach(e=>console.warn("[LinkItBe] unresolved keyword:",e.keyword));break}e.sort((e,n)=>n.spanStart-e.spanStart);const{integ:n}=e[0];u(o,n,c)&&(g+=1),h=h.filter(e=>e!==n)}return f.filter(e=>"mini-offer"===e.type).forEach(n=>{!function(n,t,o){const r=e.plainBlockSelector||"p, blockquote, h1, h2, h3, h4, h5, h6",i=n.querySelectorAll(r);if(!i.length)return console.warn("[LinkItBe] mini-offer: no content blocks"),!1;const l=i[i.length-1],s=document.createElement("p");s.className="__lb_mini_offer",s.style.cssText="margin-top:16px;font-size:15px;font-family:Manrope, sans-serif;color:#343b4c;";const c=document.createElement("span");c.style.cssText="font-size:11px;color:#999;display:block;margin-bottom:4px;",c.textContent="#реклама",s.appendChild(c);const d=document.createElement("span"),u=null!=t.text?String(t.text):"",f=null!=t.anchor_text?String(t.anchor_text):"",g=u.indexOf(p),h=g>=0?u.slice(0,g):u,w=g>=0?u.slice(g+13):"";h&&d.appendChild(document.createTextNode(h));const m=a(t,f,o);return m?(d.appendChild(m),w&&d.appendChild(document.createTextNode(w)),s.appendChild(d),l.after(s),!0):(console.warn("[LinkItBe] mini-offer invalid integration link:",t.link),!1)}(o,n,c)?console.warn("[LinkItBe] mini-offer failed"):g+=1}),s&&o.querySelectorAll(".__lb_link").forEach(e=>{let t;try{t=JSON.parse(decodeURIComponent(e.dataset.lbIntegration||""))}catch(e){return void console.warn("[LinkItBe] invalid integration payload for preview",e)}t.preview&&t.preview.img&&(e.addEventListener("mouseenter",o=>{!function(e,n,t,o,r){if(!n.preview||!n.preview.img)return void(e.style.display="none");e.replaceChildren();const i=document.createElement("img");i.src=String(n.preview.img),i.style.cssText="width:100%;border-radius:8px;display:block;",e.appendChild(i);const l=document.createElement("div");l.style.cssText="margin-top:8px;font-size:13px;color:#343b4c;font-weight:600;",l.textContent=n.preview.title?String(n.preview.title):"",e.appendChild(l);const s=document.createElement("div");s.style.cssText="font-size:12px;color:#777;margin-top:2px;",s.textContent=n.preview.price?String(n.preview.price):"",e.appendChild(s);const c=document.createElement("div");c.style.cssText=`font-size:12px;color:${r.link_color};margin-top:2px;`,c.textContent=n.preview.cta?String(n.preview.cta):"",e.appendChild(c),e.style.left=t+16+"px",e.style.top=o-60+"px",e.style.display="block"}(s,t,o.clientX,o.clientY,i),"1"!==e.dataset.lbPreviewSent&&n&&(e.dataset.lbPreviewSent="1",r("preview_viewed","/v1/events/preview-viewed",n,t))}),e.addEventListener("mousemove",e=>{s.style.left=e.clientX+16+"px",s.style.top=e.clientY-60+"px"}),e.addEventListener("mouseleave",()=>{s.style.display="none"}))}),function(e,n){if(!e)return;const t=new IntersectionObserver(n=>{for(let o=0;o<n.length;o++){const i=n[o];if(!i.isIntersecting||i.intersectionRatio<.5)continue;const l=i.target;if("1"===l.dataset.lbViewableSent)continue;let s;try{s=JSON.parse(decodeURIComponent(l.dataset.lbIntegration||""))}catch(e){t.unobserve(l);continue}l.dataset.lbViewableSent="1",r("integration_viewable","/v1/events/integration-viewable",e,s),t.unobserve(l)}},{threshold:[.5]});n.querySelectorAll(".__lb_link").forEach(e=>t.observe(e))}(n,o),{appliedCount:g,integrations:f}}async function g(n,t){if(!function(e,n){return e?!!n||(console.warn("[LinkItBe] Content block not found for article:",e),!1):(console.warn("[LinkItBe] Article ID not found"),!1)}(n,t))return;if("1"===t.dataset.lbProcessed)return;const o=await async function(n){const t=`${String(e.apiBaseUrl||"https://api.linkitbe.com").replace(/\/$/,"")}/v1/markup?article_id=${encodeURIComponent(n)}&domain=${encodeURIComponent(e.domain)}`,o={Accept:"application/json"},r=e.partnerApiKey?String(e.partnerApiKey).trim():"";r&&(o["X-Partner-Api-Key"]=r);const i=await fetch(t,{method:"GET",headers:o,credentials:"omit"});return i.ok?await i.json():null}(n);o&&await async function(e,n,t){if("1"===n.dataset.lbProcessed)return;n.dataset.lbProcessed="1";const o=f(e,Array.isArray(t.integrations)?t.integrations:[],n,t.config.style);await i(e,o.integrations,o.appliedCount)}(n,t,o)}function h(t){if(t.nodeType!==Node.ELEMENT_NODE)return;const o=t.querySelector(e.contentSelector);if(!o)return;g(n(o),o)}function w(e){if(!e)return void console.warn("[LinkItBe] feedRoot not provided for auto markup");new MutationObserver(e=>{for(const n of e)for(let e=0;e<n.addedNodes.length;e++)h(n.addedNodes[e])}).observe(e,{childList:!0,subtree:!0})}async function m(){const t=document.querySelector(e.contentSelector),o=n(t);if(await g(o,t),!0!==e.enableFeedAutoProcess)return;const r=document.querySelector(e.feedObserverRootSelector);r?w(r):console.warn("[LinkItBe] feedObserverRootSelector not found:",e.feedObserverRootSelector)}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",m):!0===e.bootOnLoad&&m(),window.LinkItBe=Object.assign({},window.LinkItBe||{},{initArticle:g,startFeedAutoMarkup:w})}();
1
+ !function(){const e=Object.assign({domain:window.location.hostname,bootOnLoad:!0,contentSelector:null,showPreview:!0,plainBlockJoiner:"\n",plainBlockSelector:"p, blockquote, h1, h2, h3, h4, h5, h6",allowedFormats:["keyword","mini-offer"],apiBaseUrl:"https://api.linkitbe.com",partnerApiKey:null,articleIdResolver:null,enableFeedAutoProcess:!1,feedObserverRootSelector:null,fetchImpressionTokenForViewable:!0,sdkVersion:"1.0.8",styles:{link_color:"#e2187b",link_weight:"700",link_decoration:"underline"}},window.LinkItBeConfig||{});function n(n){if("function"==typeof e.articleIdResolver)try{const t=e.articleIdResolver(n),o=null!=t?String(t).trim():"";if(o)return o}catch(e){console.warn("[LinkItBe] articleIdResolver failed",e)}return null}function t(){const e="__lb_session_id";try{const n=window.localStorage.getItem(e);if(n&&n.trim())return n.trim();const t=window.crypto&&window.crypto.randomUUID?window.crypto.randomUUID():`${Date.now()}-${Math.random().toString(16).slice(2)}`;return window.localStorage.setItem(e,t),t}catch(e){return`${Date.now()}-${Math.random().toString(16).slice(2)}`}}function o(e){if(!Array.isArray(e))return null;for(const n of e){const e=n&&n.link?String(n.link):"";if(e)try{const n=new URL(e,window.location.origin).searchParams.get("processing_id"),t=Number(n);if(Number.isFinite(t)&&t>0)return t}catch(e){}}return null}async function r(n,r,i,l,s){const a=o([l]);if(!a)return;const c=e.partnerApiKey?String(e.partnerApiKey).trim():"";if(!c)return;const d=Object.assign({event_id:window.crypto&&window.crypto.randomUUID?window.crypto.randomUUID():`${Date.now()}-${Math.random().toString(16).slice(2)}`,event_type:n,event_time:(new Date).toISOString(),article_id:i,domain:e.domain,processing_id:a,placement_link:window.location.href,integration_type:l&&null!=l.type?l.type:null,sdk_version:String(e.sdkVersion||""),session_id:t(),referrer:document.referrer||null},s||{});if("integration_viewable"===n&&e.fetchImpressionTokenForViewable){const n=await async function(n,t){const o=e.partnerApiKey?String(e.partnerApiKey).trim():"";if(!o||!t)return null;try{const r=String(e.apiBaseUrl||"https://api.linkitbe.com").replace(/\/$/,""),i=new URL(r+"/v1/events/integration-viewable-token");i.searchParams.set("article_id",n),i.searchParams.set("domain",String(e.domain||"")),i.searchParams.set("processing_id",String(t));const l=await fetch(i.toString(),{method:"GET",headers:{Accept:"application/json","X-Partner-Api-Key":o},credentials:"omit"});if(!l.ok)return null;const s=await l.json();return s&&s.impression_token?String(s.impression_token):null}catch(e){return console.warn("[LinkItBe] impression token fetch failed",e),null}}(i,a);n&&(d.impression_token=n)}try{const n=String(e.apiBaseUrl||"https://api.linkitbe.com").replace(/\/$/,"");await fetch(`${n}${r}`,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json","X-Partner-Api-Key":c},body:JSON.stringify(d),keepalive:!0,credentials:"omit"})}catch(e){console.warn("[LinkItBe] failed to send "+n,e)}}async function i(e,n,t){if(t<=0)return;const i=function(e){if(!Array.isArray(e))return null;for(let n=0;n<e.length;n++){const t=e[n];if(t&&o([t]))return t}return null}(n);i&&await r("view_rendered","/v1/events/view-rendered",e,i,{integrations_count:t})}function l(n){const t=e.allowedFormats;return null!=t&&Array.isArray(t)?n.filter(e=>t.includes(e.type)):n}function s(e,n,t){const o=12,r=e.offsetHeight||0,i=e.offsetWidth||0;let l=n+16,s=t-r+60;const a=window.innerWidth-i-o;l=Math.min(Math.max(l,o),Math.max(o,a));s+r>window.innerHeight-o&&(s=t-r-o),e.style.left=l+"px",e.style.top=Math.max(o,s)+"px"}function a(e){const n=[],t=document.createTreeWalker(e,NodeFilter.SHOW_TEXT,{acceptNode:e=>function(e){const n=e.nodeType===Node.TEXT_NODE?e.parentElement:e;return!!(n&&n.closest&&n.closest("a.__lb_link"))}(e)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT});let o;for(;o=t.nextNode();)n.push(o);return n}function c(e,n){if(!n)return[];const t=new RegExp(String(n).replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),"gi");const o=[];let r;for(;null!==(r=t.exec(e));)o.push({start:r.index,end:r.index+r[0].length}),0===r[0].length&&t.lastIndex===r.index&&t.lastIndex++;return o}function d(e){if(null==e)return null;try{const n=new URL(String(e),window.location.origin);return"http:"!==n.protocol&&"https:"!==n.protocol?null:n.toString()}catch(e){return null}}function u(e,n){const t=e&&null!=e.erid?String(e.erid).trim():"",o=d(e&&e.advertiser_info_url);if(o)return function(e,n){const t=null!=n?String(n).trim():"";if(!t||!e)return e;try{const n=new URL(String(e),window.location.origin);return n.searchParams.set("erid",t),n.toString()}catch(n){return e}}(o,t);const r=n&&n.advertiser_info_base_url,i=e&&null!=e.advertiser?String(e.advertiser).trim():"";if(!r||!i)return null;try{const e=new URL(String(r),window.location.origin);return e.searchParams.append("items[]",i),t&&e.searchParams.set("erid",t),e.toString()}catch(e){return null}}function p(e,n,t){const o=e&&"object"==typeof e?e:{},r=o.marker_type||"plain_text",i=o.position||"before_phrase",l=null!=o.marker_text?String(o.marker_text).trim():"",s="font-size:11px;color:#999;display:block;margin-bottom:4px;";if("marker_link"===r){const e=l||"#реклама",o=function(e,n,t,o){const r=u(e,o);if(!r)return console.warn("[LinkItBe] advertiser info url is missing for marker",e&&e.advertiser),null;const i=document.createElement("a");return i.href=r,i.target="_blank",i.rel="noopener noreferrer sponsored nofollow",i.className="__lb_link __lb_advertiser_info_link",i.style.cssText=t.replace(/\s+/g," ").trim(),i.textContent=n,i}(n,e,"font-size:11px;color:#999;font-weight:400;text-decoration:underline;cursor:pointer;",t);if(o){const e=document.createElement("span");return e.style.cssText=s.replace("margin-bottom:4px;",""),e.appendChild(o),{node:e,position:i}}console.warn("[LinkItBe] marker_link fallback to plain text:",e);const r=document.createElement("span");return r.style.cssText=s,r.textContent=e,{node:r,position:i}}"plain_text"!==r&&console.warn("[LinkItBe] unsupported marker_type, fallback to plain_text:",r);const a=document.createElement("span");return a.style.cssText=s,a.textContent=function(e){const n=String(e||"#реклама").trim();return n?n.endsWith(":")?n:n.replace(/:+$/,"")+":":"#реклама"}(l||"#реклама"),{node:a,position:i}}function f(e,n,t){const o=d(e.link);if(!o)return null;const r=document.createElement("a");return r.href=o,r.target="_blank",r.rel="noopener noreferrer sponsored nofollow",r.className="__lb_link",r.setAttribute("data-lb-integration",encodeURIComponent(JSON.stringify(e))),r.style.cssText=t.replace(/\s+/g," ").trim(),r.textContent=n,r}function m(n,t){const{plain:o,spans:r}=function(n){const t=null!=e.plainBlockJoiner?e.plainBlockJoiner:"\n",o=e.plainBlockSelector||"p, blockquote, h1, h2, h3, h4, h5, h6",r=n.querySelectorAll(o),i=[];let l="";return r.forEach((e,n)=>{n>0&&(l+=t);const o=a(e);for(const e of o){const n=e.textContent;if(!n.length)continue;const t=l.length;l+=n,i.push({g0:t,g1:l.length,node:e})}}),{plain:l,spans:i}}(n),i=function(e,n){if(!e.length)return null;if(1===e.length)return e[0];const t=n||{},o=t.match_index;if(null!=o&&Number.isFinite(Number(o))){const n=Number(o);if(n>=0&&n<e.length)return e[n];console.warn("[LinkItBe] match_index out of range:",o,"n=",e.length)}const r=t.offset_start,i=t.offset_end;if(null!=r&&Number.isFinite(Number(r))){const n=Number(r),t=null!=i&&Number.isFinite(Number(i))?Number(i):null;let o=e[0],l=1/0;for(const r of e){const e=1e9*Math.abs(r.start-n)+(null!=t?Math.abs(r.end-t):0);e<l&&(l=e,o=r)}return o}return console.warn("[LinkItBe] ambiguous matches, skipping (no match_index / offset_start):",e.length),null}(c(o,t.keyword),t.position);if(!i)return null;const l=o.slice(i.start,i.end);return{plain:o,spans:r,span:i,linkText:l}}function w(e,n,t){const o=m(e,n);if(!o)return console.warn("[LinkItBe] no span for keyword:",n.keyword),!1;const{plain:r,spans:i,span:l,linkText:s}=o,a=function(e,n,t){if(e<0||e>=t||!n.length)return null;const o=n.find(n=>e>=n.g0&&e<n.g1);return o?{node:o.node,offset:e-o.g0}:null}(l.start,i,r.length),c=function(e,n,t){if(!n.length)return null;if(e<=0)return{node:n[0].node,offset:0};if(e>=t){const e=n[n.length-1];return{node:e.node,offset:e.node.textContent.length}}for(let t=0;t<n.length;t++){const o=n[t];if(e>o.g0&&e<=o.g1)return{node:o.node,offset:e-o.g0};if(e===o.g0)return{node:o.node,offset:0}}for(let t=0;t<n.length;t++)if(e<n[t].g0)return{node:n[t].node,offset:0};return null}(l.end,i,r.length);if(!a||!c)return console.warn("[LinkItBe] could not map span to DOM"),!1;const d=r.slice(l.start,l.end);if(d.toLowerCase()!==s.toLowerCase())return console.warn("[LinkItBe] plain slice mismatch"),!1;const u=document.createRange();u.setStart(a.node,a.offset),u.setEnd(c.node,c.offset);const p=f(n,d,t);return p?(function(e,n){try{e.surroundContents(n)}catch(t){const o=e.extractContents();for(;o.firstChild;)n.appendChild(o.firstChild);e.insertNode(n)}}(u,p),!0):(console.warn("[LinkItBe] invalid integration link:",n.link),!1)}const h="{anchor_text}";function g(n,t,o,i,a,c){if(!o){console.warn("[LinkItBe] Content block not provided");return{appliedCount:0,integrations:l(t)}}const d=e.showPreview?function(){const e=document.createElement("div");return e.id="__lb_preview",e.style.cssText="\n position: fixed;\n display: none;\n background: white;\n border-radius: 12px;\n box-shadow: 0 4px 20px rgba(0,0,0,0.15);\n padding: 12px;\n z-index: 99999;\n width: 180px;\n font-family: Manrope, sans-serif;\n pointer-events: none;\n ",document.body.appendChild(e),e}():null,u=`\n color:${i.link_color};\n font-weight:${i.link_weight};\n text-decoration:${i.link_decoration};\n cursor:pointer;\n `,g=l(t);let b=0,_=g.filter(e=>"keyword"===e.type);for(;_.length;){const e=[];for(const n of _){const t=m(o,n);t&&e.push({integ:n,spanStart:t.span.start})}if(!e.length){_.forEach(e=>console.warn("[LinkItBe] unresolved keyword:",e.keyword));break}e.sort((e,n)=>n.spanStart-e.spanStart);const{integ:n}=e[0];w(o,n,u)&&(b+=1),_=_.filter(e=>e!==n)}return g.filter(e=>"mini-offer"===e.type).forEach(e=>{!function(e,n,t,o,r){if(!e)return console.warn("[LinkItBe] mini-offer: no content block"),!1;const i=document.createElement("p");i.className="__lb_mini_offer",i.style.cssText="margin-top:16px;font-size:15px;font-family:Manrope, sans-serif;color:#343b4c;";const l=p(o,n,r),s=document.createElement("span"),a=null!=n.text?String(n.text):"",c=null!=n.anchor_text?String(n.anchor_text):"",d=a.indexOf(h),u=d>=0?a.slice(0,d):a,m=d>=0?a.slice(d+13):"";u&&s.appendChild(document.createTextNode(u));const w=f(n,c,t);return w?(s.appendChild(w),m&&s.appendChild(document.createTextNode(m)),l&&"before_phrase"===l.position&&i.appendChild(l.node),i.appendChild(s),l&&"after_phrase"===l.position&&(i.appendChild(document.createElement("br")),i.appendChild(l.node)),e.appendChild(i),!0):(console.warn("[LinkItBe] mini-offer invalid integration link:",n.link),!1)}(o,e,u,a,c)?console.warn("[LinkItBe] mini-offer failed"):b+=1}),d&&o.querySelectorAll(".__lb_link").forEach(e=>{let t;try{t=JSON.parse(decodeURIComponent(e.dataset.lbIntegration||""))}catch(e){return void console.warn("[LinkItBe] invalid integration payload for preview",e)}t.preview&&t.preview.img&&(e.addEventListener("mouseenter",o=>{!function(e,n,t,o,r){if(!n.preview||!n.preview.img)return void(e.style.display="none");e.replaceChildren();const i=document.createElement("img");i.src=String(n.preview.img),i.style.cssText="width:100%;border-radius:8px;display:block;",e.appendChild(i);const l=document.createElement("div");l.style.cssText="margin-top:8px;font-size:13px;color:#343b4c;font-weight:600;",l.textContent=n.preview.title?String(n.preview.title):"",e.appendChild(l);const a=document.createElement("div");a.style.cssText="font-size:12px;color:#777;margin-top:2px;",a.textContent=n.preview.price?String(n.preview.price):"",e.appendChild(a);const c=document.createElement("div");c.style.cssText=`font-size:12px;color:${r.link_color};margin-top:2px;`,c.textContent=n.preview.cta?String(n.preview.cta):"",e.appendChild(c),e.style.display="block",s(e,t,o)}(d,t,o.clientX,o.clientY,i),"1"!==e.dataset.lbPreviewSent&&n&&(e.dataset.lbPreviewSent="1",r("preview_viewed","/v1/events/preview-viewed",n,t))}),e.addEventListener("mousemove",e=>{s(d,e.clientX,e.clientY)}),e.addEventListener("mouseleave",()=>{d.style.display="none"}))}),function(e,n){if(!e)return;const t=e=>{const n=e._lbViewDwellTimer;n&&(clearTimeout(n),e._lbViewDwellTimer=null)},o=new IntersectionObserver(n=>{for(let i=0;i<n.length;i++){const l=n[i],s=l.target;"1"!==s.dataset.lbViewableSent&&(!l.isIntersecting||l.intersectionRatio<.5?(t(s),s._lbViewVisibleSince=null,s._lbViewLastIr=null):(s._lbViewLastIr=String(Math.min(1,Math.max(0,l.intersectionRatio))),s._lbViewVisibleSince||(s._lbViewVisibleSince=Date.now()),t(s),s._lbViewDwellTimer=setTimeout(()=>{if(s._lbViewDwellTimer=null,"1"===s.dataset.lbViewableSent)return;if("visible"!==document.visibilityState)return;let n;try{n=JSON.parse(decodeURIComponent(s.dataset.lbIntegration||""))}catch(e){return void o.unobserve(s)}s.dataset.lbViewableSent="1";const t=s._lbViewVisibleSince||Date.now(),i=Math.max(0,Date.now()-t),l=parseFloat(s._lbViewLastIr||"0.5");r("integration_viewable","/v1/events/integration-viewable",e,n,{tab_visible:"visible"===document.visibilityState,dwell_ms:Math.max(1e3,i),intersection_ratio:l,viewability_rule_version:"elitesdk-1"}),o.unobserve(s)},1e3)))}},{threshold:[.5]});n.querySelectorAll(".__lb_link").forEach(e=>o.observe(e))}(n,o),{appliedCount:b,integrations:g}}async function b(n,t){if(!function(e,n){return e?!!n||(console.warn("[LinkItBe] Content block not found for article:",e),!1):(console.warn("[LinkItBe] Article ID not found"),!1)}(n,t))return;if("1"===t.dataset.lbProcessed)return;const o=await async function(n){const t=`${String(e.apiBaseUrl||"https://api.linkitbe.com").replace(/\/$/,"")}/v1/markup?article_id=${encodeURIComponent(n)}&domain=${encodeURIComponent(e.domain)}`,o={Accept:"application/json"},r=e.partnerApiKey?String(e.partnerApiKey).trim():"";r&&(o["X-Partner-Api-Key"]=r);const i=await fetch(t,{method:"GET",headers:o,credentials:"omit"});return i.ok?await i.json():null}(n);o&&await async function(e,n,t){if("1"===n.dataset.lbProcessed)return;n.dataset.lbProcessed="1";const o=Array.isArray(t.integrations)?t.integrations:[],r=t.config&&"object"==typeof t.config?t.config:{},l=g(e,o,n,r.style||{},r.labeling_template||null,r);await i(e,l.integrations,l.appliedCount)}(n,t,o)}function _(t){if(t.nodeType!==Node.ELEMENT_NODE)return;const o=t.querySelector(e.contentSelector);if(!o)return;b(n(o),o)}function k(e){if(!e)return void console.warn("[LinkItBe] feedRoot not provided for auto markup");new MutationObserver(e=>{for(const n of e)for(let e=0;e<n.addedNodes.length;e++)_(n.addedNodes[e])}).observe(e,{childList:!0,subtree:!0})}async function y(){const t=document.querySelector(e.contentSelector),o=n(t);if(await b(o,t),!0!==e.enableFeedAutoProcess)return;const r=document.querySelector(e.feedObserverRootSelector);r?k(r):console.warn("[LinkItBe] feedObserverRootSelector not found:",e.feedObserverRootSelector)}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",y):!0===e.bootOnLoad&&y(),window.LinkItBe=Object.assign({},window.LinkItBe||{},{initArticle:b,startFeedAutoMarkup:k})}();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linkitbe/sdk",
3
- "version": "1.0.2",
3
+ "version": "1.0.8",
4
4
  "description": "LinkItBe browser SDK",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": {