@playpilot/tpi 8.10.3 → 8.10.4-beta.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playpilot/tpi",
3
- "version": "8.10.3",
3
+ "version": "8.10.4-beta.2",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -6,6 +6,7 @@ import type { LinkInjectionResponse, LinkInjection } from '../types/injection'
6
6
  import { getFullUrlPath } from '../url'
7
7
  import { api } from './api'
8
8
  import { getApiToken } from '$lib/token'
9
+ import { getPageTextAndElements } from '$lib/injectionElements'
9
10
 
10
11
  let pollTimeout: ReturnType<typeof setTimeout> | null = null
11
12
 
@@ -13,19 +14,22 @@ let pollTimeout: ReturnType<typeof setTimeout> | null = null
13
14
  * Fetch link injections for a URL. This will be either a POST or a GET request depending on whether on not the AI should run.
14
15
  */
15
16
  export async function fetchLinkInjections(
16
- pageText: string | null,
17
- { url = getFullUrlPath(), hash = stringToHash(pageText || ''), params = {}, method = 'GET' }:
18
- { url?: string, hash?: string, params?: Record<string, any>, method?: 'GET' | 'POST' } = {},
17
+ { url = getFullUrlPath(), params = {}, method = 'GET' }:
18
+ { url?: string, params?: Record<string, any>, method?: 'GET' | 'POST' } = {},
19
19
  ): Promise<LinkInjectionResponse> {
20
20
  const apiToken = getApiToken()
21
21
  const isEditorialMode = isEditorialModeEnabled() ? await authorize() : false
22
22
  const language = getLanguage()
23
+ const { pageText } = getPageTextAndElements()
24
+ const hash = stringToHash(pageText || '')
23
25
 
24
26
  if (!apiToken) throw new Error('No token was provided')
25
27
 
26
28
  const apiUrl = `/external-pages/?api-token=${apiToken}&include_title_details=true${isEditorialMode ? '&editorial_mode_enabled=true' : ''}&language=${language}`
27
29
  let response: LinkInjectionResponse
28
30
 
31
+ console.log('page text in fetchLinkInjections', { pageText })
32
+
29
33
  // We use separate requests when running the AI or setting the editor session vs when only getting the results.
30
34
  // For regular requests we use a GET endpoint, but when saving data we POST to the same url.
31
35
  if (method === 'POST') {
@@ -60,16 +64,19 @@ export async function fetchLinkInjections(
60
64
  * The results return `injections_ready=false` while the injections are not yet ready.
61
65
  */
62
66
  export async function pollLinkInjections(
63
- pageText: string,
64
67
  { requireCompletedResult = false, runAiWhenRelevant = false, pollInterval = 3000, maxTries = 600, onpoll = () => null }:
65
68
  { requireCompletedResult?: boolean, runAiWhenRelevant?: boolean, pollInterval?: number, maxTries?: number, onpoll?: (_response: LinkInjectionResponse) => void } = {},
66
69
  ): Promise<LinkInjectionResponse> {
70
+ const { pageText } = getPageTextAndElements()
71
+
67
72
  let currentTry = 0
68
73
 
69
74
  // Clear pollTimeout if it is already running to prevent multiple timeouts from running at the same time
70
75
  // This is mostly handy during HMR, but also during navigation changes
71
76
  if (pollTimeout) clearTimeout(pollTimeout)
72
77
 
78
+ console.log('page text in pollLinkInjections', { pageText })
79
+
73
80
  const poll = async (resolve: Function, reject: Function): Promise<void> => {
74
81
  let response
75
82
  try {
@@ -77,14 +84,14 @@ export async function pollLinkInjections(
77
84
  // if it does. We're still interested in handling the response from here and don't want it to short circuit
78
85
  // just because the fetch failed.
79
86
  try {
80
- response = await fetchLinkInjections(pageText)
87
+ response = await fetchLinkInjections()
81
88
  } catch(error: any) {
82
89
  if (error?.status === 404) {
83
90
  // If the GET endpoint returns a 404 no article has been generated yet and we can safely generate
84
91
  // a new article. We can safely throw here (meaning we poll again) as nothing will be returned.
85
92
  // The first POST indexes the article, the second runs the AI if needed.
86
- response = await fetchLinkInjections(pageText, { method: 'POST' })
87
- if (response.ai_enabled) response = await fetchLinkInjections(pageText, { params: { run_ai: true }, method: 'POST' })
93
+ response = await fetchLinkInjections({ method: 'POST' })
94
+ if (response.ai_enabled) response = await fetchLinkInjections({ params: { run_ai: true }, method: 'POST' })
88
95
  }
89
96
 
90
97
  throw new Error
@@ -133,10 +140,10 @@ export async function runAiBasedOnResponse(pageText: string, response: LinkInjec
133
140
  if (response.ai_last_run && isCurrentTimeEqualToResponseTime) return response
134
141
  if (new Date(currentModifiedTime || 0) <= new Date(responseModifiedTime || 0)) return response
135
142
 
136
- return await fetchLinkInjections(pageText, { params: { run_ai: true }, method: 'POST' })
143
+ return await fetchLinkInjections({ params: { run_ai: true }, method: 'POST' })
137
144
  }
138
145
 
139
- export async function saveLinkInjections(linkInjections: LinkInjection[], pageText: string): Promise<LinkInjection[]> {
146
+ export async function saveLinkInjections(linkInjections: LinkInjection[]): Promise<LinkInjection[]> {
140
147
  const selector = window.PlayPilotLinkInjections?.selector
141
148
 
142
149
  // Only save manual injections, AI injections should be left intact and can't be saved over
@@ -156,7 +163,7 @@ export async function saveLinkInjections(linkInjections: LinkInjection[], pageTe
156
163
  removed: !!linkInjection.removed,
157
164
  }))
158
165
 
159
- const response = await fetchLinkInjections(pageText, {
166
+ const response = await fetchLinkInjections({
160
167
  method: 'POST',
161
168
  params: {
162
169
  private_token: getAuthToken(),
@@ -11,10 +11,9 @@ export const sessionPeriodMilliseconds = sessionPollPeriodMilliseconds * 2
11
11
  * where we save the session.
12
12
  * Along with that, we also return ai_enabled and injections_enabled as they are used to update the state of
13
13
  * the current editing session.
14
- * @param pageText Despite not being used, the request still requires a valid pageText string
15
14
  */
16
- export async function fetchAsSession(pageText: string): Promise<SessionResponse> {
17
- const { ai_enabled, injections_enabled, ai_last_run, session_id, session_last_ping } = await fetchLinkInjections(pageText)
15
+ export async function fetchAsSession(): Promise<SessionResponse> {
16
+ const { ai_enabled, injections_enabled, ai_last_run, session_id, session_last_ping } = await fetchLinkInjections()
18
17
 
19
18
  const isCurrentlyAllowToEdit = isAllowedToEdit(session_id || null, session_last_ping || null)
20
19
 
@@ -28,15 +27,14 @@ export async function fetchAsSession(pageText: string): Promise<SessionResponse>
28
27
 
29
28
  if (!isCurrentlyAllowToEdit) return response
30
29
 
31
- return await saveCurrentSession(pageText)
30
+ return await saveCurrentSession()
32
31
  }
33
32
 
34
33
  /**
35
34
  * Save the current users session id as the currently active session. This is always completed regardless of if the user
36
35
  * is the current owner of the session, so check that first if necessary!
37
- * @param pageText Despite not being used, the request still requires a valid pageText string
38
36
  */
39
- export async function saveCurrentSession(pageText: string): Promise<SessionResponse> {
37
+ export async function saveCurrentSession(): Promise<SessionResponse> {
40
38
  const sessionId = getSessionId()
41
39
  const now = new Date(Date.now()).toISOString()
42
40
 
@@ -45,7 +43,7 @@ export async function saveCurrentSession(pageText: string): Promise<SessionRespo
45
43
  session_last_ping: now.toString(),
46
44
  }
47
45
 
48
- const { ai_enabled, injections_enabled, ai_last_run } = await fetchLinkInjections(pageText, { params })
46
+ const { ai_enabled, injections_enabled, ai_last_run } = await fetchLinkInjections({ params })
49
47
 
50
48
  return {
51
49
  ai_enabled,
@@ -91,9 +91,9 @@ export function getPageText(elements: HTMLElement[]): string {
91
91
  return elements.map(element => element.innerText).join('\n\n')
92
92
  }
93
93
 
94
- export function getPageTextAndElements(config: ConfigResponse | null): { parentElement: HTMLElement, elements: HTMLElement[], pageText: string } {
94
+ export function getPageTextAndElements(config: ConfigResponse = window.PlayPilotLinkInjections?.config): { parentElement: HTMLElement, elements: HTMLElement[], pageText: string } {
95
95
  const parentElement = getLinkInjectionsParentElement()
96
- const elements = getLinkInjectionElements(parentElement, config?.exclude_elements_selector || '')
96
+ const elements = getLinkInjectionElements(parentElement, config.exclude_elements_selector || '')
97
97
  const pageText = getPageText(elements)
98
98
 
99
99
  return { parentElement, elements, pageText }
package/src/main.ts CHANGED
@@ -3,7 +3,6 @@ import { fetchConfig } from '$lib/api/config'
3
3
  import { pollLinkInjections } from '$lib/api/externalPages'
4
4
  import { setConsent } from '$lib/consent'
5
5
  import { isCrawler } from '$lib/crawler'
6
- import { getPageTextAndElements } from '$lib/injectionElements'
7
6
  import { isUrlExcludedViaConfig } from '$lib/url'
8
7
 
9
8
  window.PlayPilotLinkInjections ||= {
@@ -70,9 +69,7 @@ window.PlayPilotLinkInjections ||= {
70
69
 
71
70
  if (isUrlExcludedViaConfig(this.config)) return
72
71
 
73
- const { pageText } = getPageTextAndElements(this.config)
74
-
75
- this.initial_link_injections_promise = pollLinkInjections(pageText, { maxTries: 1, runAiWhenRelevant: !isCrawler() })
72
+ this.initial_link_injections_promise = pollLinkInjections({ maxTries: 1, runAiWhenRelevant: !isCrawler() })
76
73
 
77
74
  this.mount()
78
75
  },
@@ -2,7 +2,6 @@
2
2
  import { onDestroy } from 'svelte'
3
3
  import { pollLinkInjections } from '$lib/api/externalPages'
4
4
  import { clearLinkInjections, injectLinksInDocument, separateLinkInjectionTypes } from '$lib/injection'
5
- import { getPageText, getPageTextAndElements } from '$lib/injectionElements'
6
5
  import { fireQueuedTrackingEvents, setTrackingSids, track } from '$lib/tracking'
7
6
  import { isUrlExcludedViaConfig } from '$lib/url'
8
7
  import { isCrawler } from '$lib/crawler'
@@ -11,6 +10,7 @@
11
10
  import { fetchConfig } from '$lib/api/config'
12
11
  import { insertExplore, insertExploreIntoNavigation } from '$lib/explore'
13
12
  import { authorize, getAuthToken, isEditorialModeEnabled, removeAuthCookie, setEditorialParamInUrl } from '$lib/api/auth'
13
+ import { getPageTextAndElements } from '$lib/injectionElements'
14
14
  import type { LinkInjectionResponse, LinkInjection, LinkInjectionTypes } from '$lib/types/injection'
15
15
  import Editor from './components/Editorial/Editor.svelte'
16
16
  import EditorTrigger from './components/Editorial/EditorTrigger.svelte'
@@ -22,8 +22,6 @@
22
22
  import PostersScrollReveal from './components/PostersScrollReveal.svelte'
23
23
  import TrackingPixelsForTitleDataPerLink from './components/TrackingPixelsForTitleDataPerLink.svelte'
24
24
 
25
- let elements: HTMLElement[] = $state([])
26
-
27
25
  let response: LinkInjectionResponse | null = $state(null)
28
26
  let isEditorialMode = $state(isEditorialModeEnabled())
29
27
  let hasAuthToken = $state(!!getAuthToken())
@@ -35,8 +33,6 @@
35
33
  // @ts-ignore It's ok if the response is empty
36
34
  const { ai_injections: aiInjections = [], manual_injections: manualInjections = [] } = $derived(response || {})
37
35
 
38
- const pageText = $derived(getPageText(elements))
39
-
40
36
  const initializePromise = initialize()
41
37
 
42
38
  $effect(() => {
@@ -86,8 +82,6 @@
86
82
  if (isUrlExcluded) return
87
83
 
88
84
  if (config?.html_selector) window.PlayPilotLinkInjections.selector = config?.html_selector
89
-
90
- elements = getPageTextAndElements(window.PlayPilotLinkInjections.config).elements
91
85
  } catch(error) {
92
86
  // We return if the config did not get fetched properly, as we can't determine what should and should
93
87
  // get injected without it.
@@ -98,7 +92,7 @@
98
92
 
99
93
  try {
100
94
  response = await window.PlayPilotLinkInjections.initial_link_injections_promise || null
101
- response ||= await pollLinkInjections(pageText, { maxTries: 1, runAiWhenRelevant: !isCrawler() })
95
+ response ||= await pollLinkInjections({ maxTries: 1, runAiWhenRelevant: !isCrawler() })
102
96
 
103
97
  loading = false
104
98
 
@@ -114,7 +108,7 @@
114
108
 
115
109
  if (!isEditorialMode || isCrawler()) return
116
110
 
117
- response = await pollLinkInjections(pageText, { requireCompletedResult: true, onpoll: (update) => response = update })
111
+ response = await pollLinkInjections({ requireCompletedResult: true, onpoll: (update) => response = update })
118
112
  inject({ aiInjections, manualInjections })
119
113
  } catch(error: unknown) {
120
114
  console.error(error)
@@ -136,6 +130,7 @@
136
130
  }
137
131
 
138
132
  function inject(injections: LinkInjectionTypes = { aiInjections, manualInjections }): void {
133
+ const { elements } = getPageTextAndElements()
139
134
  const filteredInjections = injectLinksInDocument(elements, injections)
140
135
 
141
136
  if (JSON.stringify(filteredInjections) !== JSON.stringify(linkInjections)) {
@@ -206,7 +201,6 @@
206
201
  <svelte:boundary onerror={(error) => track(TrackingEvent.EditorError, null, { message: (error as Error).message })}>
207
202
  <Editor
208
203
  bind:linkInjections
209
- {pageText}
210
204
  {loading}
211
205
  onreinitialize={reinitializeEditor}
212
206
  injectionsEnabled={response?.injections_enabled}
@@ -33,7 +33,6 @@
33
33
 
34
34
  let {
35
35
  linkInjections = $bindable(),
36
- pageText = '',
37
36
  loading = false,
38
37
  injectionsEnabled = false,
39
38
  aiStatus = {},
@@ -46,6 +45,8 @@
46
45
  const position: Position = JSON.parse(localStorage.getItem(editorPositionKey) || '{ "x": 0, "y": 0 }')
47
46
  const height: number = parseInt(localStorage.getItem(editorHeightKey) || '0')
48
47
 
48
+
49
+
49
50
  let editorElement: HTMLElement | null = $state(null)
50
51
  let manualInjectionActive = $state(false)
51
52
  let saving = $state(false)
@@ -85,7 +86,7 @@
85
86
  saving = true
86
87
  hasError = false
87
88
 
88
- linkInjections = await saveLinkInjections(linkInjections, pageText)
89
+ linkInjections = await saveLinkInjections(linkInjections)
89
90
  initialStateString = linkInjectionsString
90
91
  } catch {
91
92
  hasError = true
@@ -177,7 +178,6 @@
177
178
 
178
179
  {#if !loading}
179
180
  <Session
180
- {pageText}
181
181
  onallow={() => allowEditing = true}
182
182
  ondisallow={() => allowEditing = false}
183
183
  ontakeover={onreinitialize}
@@ -231,7 +231,6 @@
231
231
  style:top="{scrollDistance}px"
232
232
  transition:fly={{ x: Math.min(window.innerWidth, 320), duration: 200, opacity: 1 }}>
233
233
  <ManualInjection
234
- {pageText}
235
234
  onclose={() => manualInjectionActive = false}
236
235
  onsave={(linkInjection) => linkInjections.push(linkInjection)} />
237
236
  </div>
@@ -4,7 +4,7 @@
4
4
  import { onMount } from 'svelte'
5
5
  import { playPilotBaseUrl } from '$lib/constants'
6
6
  import { generateInjectionKey } from '$lib/api/externalPages'
7
- import { getLinkInjectionsParentElement } from '$lib/injectionElements'
7
+ import { getLinkInjectionsParentElement, getPageTextAndElements } from '$lib/injectionElements'
8
8
  import { heading } from '$lib/actions/heading'
9
9
  import { findSurroundingPhrases, cleanPhrase } from '$lib/text'
10
10
  import { getIndexOfSelection } from '$lib/selection'
@@ -15,12 +15,13 @@
15
15
  import TitleSearch from './Search/TitleSearch.svelte'
16
16
 
17
17
  interface Props {
18
- pageText: string
19
18
  onsave: (linkInjection: LinkInjection) => void
20
19
  onclose?: () => void
21
20
  }
22
21
 
23
- const { pageText = '', onsave, onclose = () => null }: Props = $props()
22
+ const { onsave, onclose = () => null }: Props = $props()
23
+
24
+ const { pageText } = getPageTextAndElements()
24
25
 
25
26
  let currentSelection = $state('')
26
27
  let selectionSentence = $state('')
@@ -4,7 +4,6 @@
4
4
  import Alert from './Alert.svelte'
5
5
 
6
6
  interface Props {
7
- pageText?: string,
8
7
  onpoll?: ({ injectionsEnabled, aiEnabled }: { injectionsEnabled: boolean, aiEnabled: boolean }) => void,
9
8
  onallow?: () => void,
10
9
  ondisallow?: () => void,
@@ -12,7 +11,6 @@
12
11
  }
13
12
 
14
13
  const {
15
- pageText = '',
16
14
  onpoll = () => null,
17
15
  onallow = () => null,
18
16
  ondisallow = () => null,
@@ -36,7 +34,7 @@
36
34
  }
37
35
 
38
36
  async function setSession(): Promise<void> {
39
- const result = await fetchAsSession(pageText)
37
+ const result = await fetchAsSession()
40
38
 
41
39
  if (!result) return
42
40
 
@@ -49,7 +47,7 @@
49
47
  }
50
48
 
51
49
  function takeOverEditing(): void {
52
- saveCurrentSession(pageText)
50
+ saveCurrentSession()
53
51
  ontakeover()
54
52
  }
55
53
  </script>
@@ -21,6 +21,7 @@
21
21
 
22
22
  let { navigate = () => null, searchQuery = $bindable(''), filter = $bindable({}), children }: Props = $props()
23
23
 
24
+ // svelte-ignore non_reactive_update
24
25
  let element: HTMLElement | null = null
25
26
  let height: string | null = $state(null)
26
27
  let clientWidth: number = $state(window.innerWidth)
@@ -1,105 +0,0 @@
1
- // vite._mount.config.js
2
- import path from "path";
3
- import * as dotenv from "file:///C:/Sites/playpilot-link-injection/node_modules/dotenv/lib/main.js";
4
- import { defineConfig } from "file:///C:/Sites/playpilot-link-injection/node_modules/vitest/dist/config.js";
5
- import { svelte } from "file:///C:/Sites/playpilot-link-injection/node_modules/@sveltejs/vite-plugin-svelte/src/index.js";
6
- import cssInjectedByJsPlugin from "file:///C:/Sites/playpilot-link-injection/node_modules/vite-plugin-css-injected-by-js/dist/esm/index.js";
7
-
8
- // package.json
9
- var package_default = {
10
- name: "@playpilot/tpi",
11
- version: "8.10.3",
12
- type: "module",
13
- scripts: {
14
- dev: "vite dev",
15
- "dev-build": "npx http-server . /build.html -p 3000",
16
- build: "vite build --config vite._main.config.js && vite build --config vite._mount.config.js && vite build --config vite._mount.config.js --mode editorial",
17
- preview: "vite preview",
18
- check: "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
19
- "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
20
- format: "prettier --write .",
21
- lint: "prettier --check . && eslint .",
22
- "test:unit": "vitest",
23
- test: "npm run test:unit -- --run",
24
- release: "node release.js"
25
- },
26
- devDependencies: {
27
- "@playpilot/retargeting-tracking": "^0.1.0",
28
- "@sveltejs/adapter-auto": "^7.0.1",
29
- "@sveltejs/kit": "^2.56.1",
30
- "@sveltejs/vite-plugin-svelte": "^4.0.0",
31
- "@testing-library/svelte": "^5.2.6",
32
- "@types/node": "^25.5.2",
33
- "@typescript-eslint/eslint-plugin": "^8.32.1",
34
- "@typescript-eslint/parser": "^8.32.1",
35
- dotenv: "^17.4.1",
36
- eslint: "^9.27.0",
37
- "eslint-config-prettier": "^9.1.0",
38
- "eslint-plugin-svelte": "^3.15.0",
39
- globals: "^15.0.0",
40
- "happy-dom": "^16.8.1",
41
- prettier: "^3.8.3",
42
- "prettier-plugin-svelte": "^3.2.6",
43
- sass: "^1.99.0",
44
- svelte: "5.44.1",
45
- "svelte-check": "^4.0.0",
46
- "svelte-preprocess": "^6.0.3",
47
- "svelte-tiny-slider": "^2.7.1",
48
- typescript: "^5.9.3",
49
- "typescript-eslint": "^8.59.2",
50
- vite: "^5.4.21",
51
- "vite-plugin-css-injected-by-js": "^4.0.1",
52
- vitest: "^2.1.9"
53
- }
54
- };
55
-
56
- // vite._mount.config.js
57
- var __vite_injected_original_dirname = "C:\\Sites\\playpilot-link-injection";
58
- dotenv.config({ path: ".env" });
59
- var vite_mount_config_default = defineConfig(({ mode }) => {
60
- const isEditiorial = mode === "editorial";
61
- return {
62
- plugins: [
63
- svelte(),
64
- cssInjectedByJsPlugin(),
65
- injectEnvVariables
66
- ],
67
- build: {
68
- emptyOutDir: false,
69
- rollupOptions: {
70
- input: "./src/mount.ts",
71
- output: {
72
- format: "iife",
73
- name: "PlayPilotMount",
74
- entryFileNames: isEditiorial ? "editorial.mount.js" : "mount.js"
75
- }
76
- }
77
- },
78
- resolve: {
79
- alias: {
80
- "$lib": path.resolve(__vite_injected_original_dirname, "./src/lib")
81
- }
82
- },
83
- define: {
84
- __SCRIPT_VERSION__: JSON.stringify(package_default.version),
85
- __IS_EDITORIAL_SCRIPT__: JSON.stringify(isEditiorial)
86
- }
87
- };
88
- });
89
- var injectEnvVariables = {
90
- name: "resolve-env-variables",
91
- /** @param {string} id */
92
- resolveId(id) {
93
- if (id === "$env/static/public") return "\0$env/static/public";
94
- },
95
- /** @param {string} id */
96
- load(id) {
97
- if (id === "\0$env/static/public") {
98
- return Object.entries(process.env).filter(([key]) => key.startsWith("PUBLIC_")).map(([key, value]) => `export const ${key} = ${JSON.stringify(value)};`).join("\n");
99
- }
100
- }
101
- };
102
- export {
103
- vite_mount_config_default as default
104
- };
105
- //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5fbW91bnQuY29uZmlnLmpzIiwgInBhY2thZ2UuanNvbiJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiY29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2Rpcm5hbWUgPSBcIkM6XFxcXFNpdGVzXFxcXHBsYXlwaWxvdC1saW5rLWluamVjdGlvblwiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiQzpcXFxcU2l0ZXNcXFxccGxheXBpbG90LWxpbmstaW5qZWN0aW9uXFxcXHZpdGUuX21vdW50LmNvbmZpZy5qc1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vQzovU2l0ZXMvcGxheXBpbG90LWxpbmstaW5qZWN0aW9uL3ZpdGUuX21vdW50LmNvbmZpZy5qc1wiO2ltcG9ydCBwYXRoIGZyb20gJ3BhdGgnXHJcbmltcG9ydCAqIGFzIGRvdGVudiBmcm9tICdkb3RlbnYnXHJcbmltcG9ydCB7IGRlZmluZUNvbmZpZyB9IGZyb20gJ3ZpdGVzdC9jb25maWcnXHJcbmltcG9ydCB7IHN2ZWx0ZSB9IGZyb20gJ0BzdmVsdGVqcy92aXRlLXBsdWdpbi1zdmVsdGUnXHJcbmltcG9ydCBjc3NJbmplY3RlZEJ5SnNQbHVnaW4gZnJvbSAndml0ZS1wbHVnaW4tY3NzLWluamVjdGVkLWJ5LWpzJ1xyXG5pbXBvcnQgcGFja2FnZUpzb24gZnJvbSAnLi9wYWNrYWdlLmpzb24nXHJcblxyXG5kb3RlbnYuY29uZmlnKHsgcGF0aDogJy5lbnYnIH0pXHJcblxyXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoKHsgbW9kZSB9KSA9PiB7XHJcbiAgY29uc3QgaXNFZGl0aW9yaWFsID0gbW9kZSA9PT0gJ2VkaXRvcmlhbCdcclxuXHJcbiAgcmV0dXJuIHtcclxuICAgIHBsdWdpbnM6IFtcclxuICAgICAgc3ZlbHRlKCksXHJcbiAgICAgIGNzc0luamVjdGVkQnlKc1BsdWdpbigpLFxyXG4gICAgICBpbmplY3RFbnZWYXJpYWJsZXMsXHJcbiAgICBdLFxyXG5cclxuICAgIGJ1aWxkOiB7XHJcbiAgICAgIGVtcHR5T3V0RGlyOiBmYWxzZSxcclxuICAgICAgcm9sbHVwT3B0aW9uczoge1xyXG4gICAgICAgIGlucHV0OiAnLi9zcmMvbW91bnQudHMnLFxyXG4gICAgICAgIG91dHB1dDoge1xyXG4gICAgICAgICAgZm9ybWF0OiAnaWlmZScsXHJcbiAgICAgICAgICBuYW1lOiAnUGxheVBpbG90TW91bnQnLFxyXG4gICAgICAgICAgZW50cnlGaWxlTmFtZXM6IGlzRWRpdGlvcmlhbCA/ICdlZGl0b3JpYWwubW91bnQuanMnIDogJ21vdW50LmpzJyxcclxuICAgICAgICB9LFxyXG4gICAgICB9LFxyXG4gICAgfSxcclxuXHJcbiAgICByZXNvbHZlOiB7XHJcbiAgICAgIGFsaWFzOiB7XHJcbiAgICAgICAgJyRsaWInOiBwYXRoLnJlc29sdmUoX19kaXJuYW1lLCAnLi9zcmMvbGliJyksXHJcbiAgICAgIH0sXHJcbiAgICB9LFxyXG5cclxuICAgIGRlZmluZToge1xyXG4gICAgICBfX1NDUklQVF9WRVJTSU9OX186IEpTT04uc3RyaW5naWZ5KHBhY2thZ2VKc29uLnZlcnNpb24pLFxyXG4gICAgICBfX0lTX0VESVRPUklBTF9TQ1JJUFRfXzogSlNPTi5zdHJpbmdpZnkoaXNFZGl0aW9yaWFsKSxcclxuICAgIH0sXHJcbiAgfVxyXG59KVxyXG5cclxuY29uc3QgaW5qZWN0RW52VmFyaWFibGVzID0ge1xyXG4gIG5hbWU6ICdyZXNvbHZlLWVudi12YXJpYWJsZXMnLFxyXG4gIC8qKiBAcGFyYW0ge3N0cmluZ30gaWQgKi9cclxuICByZXNvbHZlSWQoaWQpIHtcclxuICAgIGlmIChpZCA9PT0gJyRlbnYvc3RhdGljL3B1YmxpYycpIHJldHVybiAnXFwwJGVudi9zdGF0aWMvcHVibGljJ1xyXG4gIH0sXHJcbiAgLyoqIEBwYXJhbSB7c3RyaW5nfSBpZCAqL1xyXG4gIGxvYWQoaWQpIHtcclxuICAgIGlmIChpZCA9PT0gJ1xcMCRlbnYvc3RhdGljL3B1YmxpYycpIHtcclxuICAgICAgcmV0dXJuIE9iamVjdC5lbnRyaWVzKHByb2Nlc3MuZW52KVxyXG4gICAgICAgIC5maWx0ZXIoKFtrZXldKSA9PiBrZXkuc3RhcnRzV2l0aCgnUFVCTElDXycpKVxyXG4gICAgICAgIC5tYXAoKFtrZXksIHZhbHVlXSkgPT4gYGV4cG9ydCBjb25zdCAke2tleX0gPSAke0pTT04uc3RyaW5naWZ5KHZhbHVlKX07YClcclxuICAgICAgICAuam9pbignXFxuJylcclxuICAgIH1cclxuICB9LFxyXG59XHJcbiIsICJ7XHJcblx0XCJuYW1lXCI6IFwiQHBsYXlwaWxvdC90cGlcIixcclxuXHRcInZlcnNpb25cIjogXCI4LjEwLjNcIixcclxuXHRcInR5cGVcIjogXCJtb2R1bGVcIixcclxuXHRcInNjcmlwdHNcIjoge1xyXG5cdFx0XCJkZXZcIjogXCJ2aXRlIGRldlwiLFxyXG5cdFx0XCJkZXYtYnVpbGRcIjogXCJucHggaHR0cC1zZXJ2ZXIgLiAvYnVpbGQuaHRtbCAtcCAzMDAwXCIsXHJcblx0XHRcImJ1aWxkXCI6IFwidml0ZSBidWlsZCAtLWNvbmZpZyB2aXRlLl9tYWluLmNvbmZpZy5qcyAmJiB2aXRlIGJ1aWxkIC0tY29uZmlnIHZpdGUuX21vdW50LmNvbmZpZy5qcyAmJiB2aXRlIGJ1aWxkIC0tY29uZmlnIHZpdGUuX21vdW50LmNvbmZpZy5qcyAtLW1vZGUgZWRpdG9yaWFsXCIsXHJcblx0XHRcInByZXZpZXdcIjogXCJ2aXRlIHByZXZpZXdcIixcclxuXHRcdFwiY2hlY2tcIjogXCJzdmVsdGUta2l0IHN5bmMgJiYgc3ZlbHRlLWNoZWNrIC0tdHNjb25maWcgLi9qc2NvbmZpZy5qc29uXCIsXHJcblx0XHRcImNoZWNrOndhdGNoXCI6IFwic3ZlbHRlLWtpdCBzeW5jICYmIHN2ZWx0ZS1jaGVjayAtLXRzY29uZmlnIC4vanNjb25maWcuanNvbiAtLXdhdGNoXCIsXHJcblx0XHRcImZvcm1hdFwiOiBcInByZXR0aWVyIC0td3JpdGUgLlwiLFxyXG5cdFx0XCJsaW50XCI6IFwicHJldHRpZXIgLS1jaGVjayAuICYmIGVzbGludCAuXCIsXHJcblx0XHRcInRlc3Q6dW5pdFwiOiBcInZpdGVzdFwiLFxyXG5cdFx0XCJ0ZXN0XCI6IFwibnBtIHJ1biB0ZXN0OnVuaXQgLS0gLS1ydW5cIixcclxuXHRcdFwicmVsZWFzZVwiOiBcIm5vZGUgcmVsZWFzZS5qc1wiXHJcblx0fSxcclxuXHRcImRldkRlcGVuZGVuY2llc1wiOiB7XHJcblx0XHRcIkBwbGF5cGlsb3QvcmV0YXJnZXRpbmctdHJhY2tpbmdcIjogXCJeMC4xLjBcIixcclxuXHRcdFwiQHN2ZWx0ZWpzL2FkYXB0ZXItYXV0b1wiOiBcIl43LjAuMVwiLFxyXG5cdFx0XCJAc3ZlbHRlanMva2l0XCI6IFwiXjIuNTYuMVwiLFxyXG5cdFx0XCJAc3ZlbHRlanMvdml0ZS1wbHVnaW4tc3ZlbHRlXCI6IFwiXjQuMC4wXCIsXHJcblx0XHRcIkB0ZXN0aW5nLWxpYnJhcnkvc3ZlbHRlXCI6IFwiXjUuMi42XCIsXHJcblx0XHRcIkB0eXBlcy9ub2RlXCI6IFwiXjI1LjUuMlwiLFxyXG5cdFx0XCJAdHlwZXNjcmlwdC1lc2xpbnQvZXNsaW50LXBsdWdpblwiOiBcIl44LjMyLjFcIixcclxuXHRcdFwiQHR5cGVzY3JpcHQtZXNsaW50L3BhcnNlclwiOiBcIl44LjMyLjFcIixcclxuXHRcdFwiZG90ZW52XCI6IFwiXjE3LjQuMVwiLFxyXG5cdFx0XCJlc2xpbnRcIjogXCJeOS4yNy4wXCIsXHJcblx0XHRcImVzbGludC1jb25maWctcHJldHRpZXJcIjogXCJeOS4xLjBcIixcclxuXHRcdFwiZXNsaW50LXBsdWdpbi1zdmVsdGVcIjogXCJeMy4xNS4wXCIsXHJcblx0XHRcImdsb2JhbHNcIjogXCJeMTUuMC4wXCIsXHJcblx0XHRcImhhcHB5LWRvbVwiOiBcIl4xNi44LjFcIixcclxuXHRcdFwicHJldHRpZXJcIjogXCJeMy44LjNcIixcclxuXHRcdFwicHJldHRpZXItcGx1Z2luLXN2ZWx0ZVwiOiBcIl4zLjIuNlwiLFxyXG5cdFx0XCJzYXNzXCI6IFwiXjEuOTkuMFwiLFxyXG5cdFx0XCJzdmVsdGVcIjogXCI1LjQ0LjFcIixcclxuXHRcdFwic3ZlbHRlLWNoZWNrXCI6IFwiXjQuMC4wXCIsXHJcblx0XHRcInN2ZWx0ZS1wcmVwcm9jZXNzXCI6IFwiXjYuMC4zXCIsXHJcblx0XHRcInN2ZWx0ZS10aW55LXNsaWRlclwiOiBcIl4yLjcuMVwiLFxyXG5cdFx0XCJ0eXBlc2NyaXB0XCI6IFwiXjUuOS4zXCIsXHJcblx0XHRcInR5cGVzY3JpcHQtZXNsaW50XCI6IFwiXjguNTkuMlwiLFxyXG5cdFx0XCJ2aXRlXCI6IFwiXjUuNC4yMVwiLFxyXG5cdFx0XCJ2aXRlLXBsdWdpbi1jc3MtaW5qZWN0ZWQtYnktanNcIjogXCJeNC4wLjFcIixcclxuXHRcdFwidml0ZXN0XCI6IFwiXjIuMS45XCJcclxuXHR9XHJcbn1cclxuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUF5UyxPQUFPLFVBQVU7QUFDMVQsWUFBWSxZQUFZO0FBQ3hCLFNBQVMsb0JBQW9CO0FBQzdCLFNBQVMsY0FBYztBQUN2QixPQUFPLDJCQUEyQjs7O0FDSmxDO0FBQUEsRUFDQyxNQUFRO0FBQUEsRUFDUixTQUFXO0FBQUEsRUFDWCxNQUFRO0FBQUEsRUFDUixTQUFXO0FBQUEsSUFDVixLQUFPO0FBQUEsSUFDUCxhQUFhO0FBQUEsSUFDYixPQUFTO0FBQUEsSUFDVCxTQUFXO0FBQUEsSUFDWCxPQUFTO0FBQUEsSUFDVCxlQUFlO0FBQUEsSUFDZixRQUFVO0FBQUEsSUFDVixNQUFRO0FBQUEsSUFDUixhQUFhO0FBQUEsSUFDYixNQUFRO0FBQUEsSUFDUixTQUFXO0FBQUEsRUFDWjtBQUFBLEVBQ0EsaUJBQW1CO0FBQUEsSUFDbEIsbUNBQW1DO0FBQUEsSUFDbkMsMEJBQTBCO0FBQUEsSUFDMUIsaUJBQWlCO0FBQUEsSUFDakIsZ0NBQWdDO0FBQUEsSUFDaEMsMkJBQTJCO0FBQUEsSUFDM0IsZUFBZTtBQUFBLElBQ2Ysb0NBQW9DO0FBQUEsSUFDcEMsNkJBQTZCO0FBQUEsSUFDN0IsUUFBVTtBQUFBLElBQ1YsUUFBVTtBQUFBLElBQ1YsMEJBQTBCO0FBQUEsSUFDMUIsd0JBQXdCO0FBQUEsSUFDeEIsU0FBVztBQUFBLElBQ1gsYUFBYTtBQUFBLElBQ2IsVUFBWTtBQUFBLElBQ1osMEJBQTBCO0FBQUEsSUFDMUIsTUFBUTtBQUFBLElBQ1IsUUFBVTtBQUFBLElBQ1YsZ0JBQWdCO0FBQUEsSUFDaEIscUJBQXFCO0FBQUEsSUFDckIsc0JBQXNCO0FBQUEsSUFDdEIsWUFBYztBQUFBLElBQ2QscUJBQXFCO0FBQUEsSUFDckIsTUFBUTtBQUFBLElBQ1Isa0NBQWtDO0FBQUEsSUFDbEMsUUFBVTtBQUFBLEVBQ1g7QUFDRDs7O0FEN0NBLElBQU0sbUNBQW1DO0FBT2xDLGNBQU8sRUFBRSxNQUFNLE9BQU8sQ0FBQztBQUU5QixJQUFPLDRCQUFRLGFBQWEsQ0FBQyxFQUFFLEtBQUssTUFBTTtBQUN4QyxRQUFNLGVBQWUsU0FBUztBQUU5QixTQUFPO0FBQUEsSUFDTCxTQUFTO0FBQUEsTUFDUCxPQUFPO0FBQUEsTUFDUCxzQkFBc0I7QUFBQSxNQUN0QjtBQUFBLElBQ0Y7QUFBQSxJQUVBLE9BQU87QUFBQSxNQUNMLGFBQWE7QUFBQSxNQUNiLGVBQWU7QUFBQSxRQUNiLE9BQU87QUFBQSxRQUNQLFFBQVE7QUFBQSxVQUNOLFFBQVE7QUFBQSxVQUNSLE1BQU07QUFBQSxVQUNOLGdCQUFnQixlQUFlLHVCQUF1QjtBQUFBLFFBQ3hEO0FBQUEsTUFDRjtBQUFBLElBQ0Y7QUFBQSxJQUVBLFNBQVM7QUFBQSxNQUNQLE9BQU87QUFBQSxRQUNMLFFBQVEsS0FBSyxRQUFRLGtDQUFXLFdBQVc7QUFBQSxNQUM3QztBQUFBLElBQ0Y7QUFBQSxJQUVBLFFBQVE7QUFBQSxNQUNOLG9CQUFvQixLQUFLLFVBQVUsZ0JBQVksT0FBTztBQUFBLE1BQ3RELHlCQUF5QixLQUFLLFVBQVUsWUFBWTtBQUFBLElBQ3REO0FBQUEsRUFDRjtBQUNGLENBQUM7QUFFRCxJQUFNLHFCQUFxQjtBQUFBLEVBQ3pCLE1BQU07QUFBQTtBQUFBLEVBRU4sVUFBVSxJQUFJO0FBQ1osUUFBSSxPQUFPLHFCQUFzQixRQUFPO0FBQUEsRUFDMUM7QUFBQTtBQUFBLEVBRUEsS0FBSyxJQUFJO0FBQ1AsUUFBSSxPQUFPLHdCQUF3QjtBQUNqQyxhQUFPLE9BQU8sUUFBUSxRQUFRLEdBQUcsRUFDOUIsT0FBTyxDQUFDLENBQUMsR0FBRyxNQUFNLElBQUksV0FBVyxTQUFTLENBQUMsRUFDM0MsSUFBSSxDQUFDLENBQUMsS0FBSyxLQUFLLE1BQU0sZ0JBQWdCLEdBQUcsTUFBTSxLQUFLLFVBQVUsS0FBSyxDQUFDLEdBQUcsRUFDdkUsS0FBSyxJQUFJO0FBQUEsSUFDZDtBQUFBLEVBQ0Y7QUFDRjsiLAogICJuYW1lcyI6IFtdCn0K