PageSourceSearch

https://listings.kellycolson.com/embeds/WLEJEV

js kellycolson.com collected 2026-09-28 05:59:27 UTC 32,906 bytes, 962 lines download raw bytes

1var Penpal = (function () {
2  'use strict'
3
4  var MessageType
5  ;(function (MessageType) {
6    MessageType['Call'] = 'call'
7    MessageType['Reply'] = 'reply'
8    MessageType['Syn'] = 'syn'
9    MessageType['SynAck'] = 'synAck'
10    MessageType['Ack'] = 'ack'
11  })(MessageType || (MessageType = {}))
12
13  var Resolution
14  ;(function (Resolution) {
15    Resolution['Fulfilled'] = 'fulfilled'
16    Resolution['Rejected'] = 'rejected'
17  })(Resolution || (Resolution = {}))
18
19  var ErrorCode
20  ;(function (ErrorCode) {
21    ErrorCode['ConnectionDestroyed'] = 'ConnectionDestroyed'
22    ErrorCode['ConnectionTimeout'] = 'ConnectionTimeout'
23    ErrorCode['NoIframeSrc'] = 'NoIframeSrc'
24  })(ErrorCode || (ErrorCode = {}))
25
26  var NativeErrorName
27  ;(function (NativeErrorName) {
28    NativeErrorName['DataCloneError'] = 'DataCloneError'
29  })(NativeErrorName || (NativeErrorName = {}))
30
31  var NativeEventType
32  ;(function (NativeEventType) {
33    NativeEventType['Message'] = 'message'
34  })(NativeEventType || (NativeEventType = {}))
35
36  var createDestructor = (localName, log) => {
37    const callbacks = []
38    let destroyed = false
39    return {
40      destroy(error) {
41        if (!destroyed) {
42          destroyed = true
43          log(`${localName}: Destroying connection`)
44          callbacks.forEach((callback) => {
45            callback(error)
46          })
47        }
48      },
49
50      onDestroy(callback) {
51        destroyed ? callback() : callbacks.push(callback)
52      },
53    }
54  }
55
56  var createLogger = (debug) => {
57    /**
58     * Logs a message if debug is enabled.
59     */
60    return (...args) => {
61      if (debug) {
62        console.debug('[Penpal]', ...args)
63      }
64    }
65  }
66
67  const DEFAULT_PORT_BY_PROTOCOL = {
68    'http:': '80',
69    'https:': '443',
70  }
71  const URL_REGEX = /^(https?:)?\/\/([^/:]+)?(:(\d+))?/
72  const opaqueOriginSchemes = ['file:', 'data:']
73  /**
74   * Converts a src value into an origin.
75   */
76
77  var getOriginFromSrc = (src) => {
78    if (src && opaqueOriginSchemes.find((scheme) => src.startsWith(scheme))) {
79      // The origin of the child document is an opaque origin and its
80      // serialization is "null"
81      // https://html.spec.whatwg.org/multipage/origin.html#origin
82      return 'null'
83    } // Note that if src is undefined, then srcdoc is being used instead of src
84    // and we can follow this same logic below to get the origin of the parent,
85    // which is the origin that we will need to use.
86
87    const location = document.location
88    const regexResult = URL_REGEX.exec(src)
89    let protocol
90    let hostname
91    let port
92
93    if (regexResult) {
94      // It's an absolute URL. Use the parsed info.
95      // regexResult[1] will be undefined if the URL starts with //
96      protocol = regexResult[1] ? regexResult[1] : location.protocol
97      hostname = regexResult[2]
98      port = regexResult[4]
99    } else {
100      // It's a relative path. Use the current location's info.
101      protocol = location.protocol
102      hostname = location.hostname
103      port = location.port
104    } // If the port is the default for the protocol, we don't want to add it to the origin string
105    // or it won't match the message's event.origin.
106
107    const portSuffix = port && port !== DEFAULT_PORT_BY_PROTOCOL[protocol] ? `:${port}` : ''
108    return `${protocol}//${hostname}${portSuffix}`
109  }
110
111  /**
112   * Converts an error object into a plain object.
113   */
114  const serializeError = ({ name, message, stack }) => ({
115    name,
116    message,
117    stack,
118  })
119  /**
120   * Converts a plain object into an error object.
121   */
122
123  const deserializeError = (obj) => {
124    const deserializedError = new Error() // @ts-ignore
125
126    Object.keys(obj).forEach((key) => (deserializedError[key] = obj[key]))
127    return deserializedError
128  }
129
130  /**
131   * Listens for "call" messages coming from the remote, executes the corresponding method, and
132   * responds with the return value.
133   */
134
135  var connectCallReceiver = (info, serializedMethods, log) => {
136    const { localName, local, remote, originForSending, originForReceiving } = info
137    let destroyed = false
138
139    const handleMessageEvent = (event) => {
140      if (event.source !== remote || event.data.penpal !== MessageType.Call) {
141        return
142      }
143
144      if (originForReceiving !== '*' && event.origin !== originForReceiving) {
145        log(
146          `${localName} received message from origin ${event.origin} which did not match expected origin ${originForReceiving}`
147        )
148        return
149      }
150
151      const callMessage = event.data
152      const { methodName, args, id } = callMessage
153      log(`${localName}: Received ${methodName}() call`)
154
155      const createPromiseHandler = (resolution) => {
156        return (returnValue) => {
157          log(`${localName}: Sending ${methodName}() reply`)
158
159          if (destroyed) {
160            // It's possible to throw an error here, but it would need to be thrown asynchronously
161            // and would only be catchable using window.onerror. This is because the consumer
162            // is merely returning a value from their method and not calling any function
163            // that they could wrap in a try-catch. Even if the consumer were to catch the error,
164            // the value of doing so is questionable. Instead, we'll just log a message.
165            log(`${localName}: Unable to send ${methodName}() reply due to destroyed connection`)
166            return
167          }
168
169          const message = {
170            penpal: MessageType.Reply,
171            id,
172            resolution,
173            returnValue,
174          }
175
176          if (resolution === Resolution.Rejected && returnValue instanceof Error) {
177            message.returnValue = serializeError(returnValue)
178            message.returnValueIsError = true
179          }
180
181          try {
182            remote.postMessage(message, originForSending)
183          } catch (err) {
184            // If a consumer attempts to send an object that's not cloneable (e.g., window),
185            // we want to ensure the receiver's promise gets rejected.
186            if (err.name === NativeErrorName.DataCloneError) {
187              const errorReplyMessage = {
188                penpal: MessageType.Reply,
189                id,
190                resolution: Resolution.Rejected,
191                returnValue: serializeError(err),
192                returnValueIsError: true,
193              }
194              remote.postMessage(errorReplyMessage, originForSending)
195            }
196
197            throw err
198          }
199        }
200      }
201
202      new Promise((resolve) =>
203        resolve(serializedMethods[methodName].apply(serializedMethods, args))
204      ).then(createPromiseHandler(Resolution.Fulfilled), createPromiseHandler(Resolution.Rejected))
205    }
206
207    local.addEventListener(NativeEventType.Message, handleMessageEvent)
208    return () => {
209      destroyed = true
210      local.removeEventListener(NativeEventType.Message, handleMessageEvent)
211    }
212  }
213
214  let id = 0
215  /**
216   * @return {number} A unique ID (not universally unique)
217   */
218
219  var generateId = () => ++id
220
221  const KEY_PATH_DELIMITER = '.'
222
223  const keyPathToSegments = (keyPath) => (keyPath ? keyPath.split(KEY_PATH_DELIMITER) : [])
224
225  const segmentsToKeyPath = (segments) => segments.join(KEY_PATH_DELIMITER)
226
227  const createKeyPath = (key, prefix) => {
228    const segments = keyPathToSegments(prefix || '')
229    segments.push(key)
230    return segmentsToKeyPath(segments)
231  }
232  /**
233   * Given a `keyPath`, set it to be `value` on `subject`, creating any intermediate
234   * objects along the way.
235   *
236   * @param {Object} subject The object on which to set value.
237   * @param {string} keyPath The key path at which to set value.
238   * @param {Object} value The value to store at the given key path.
239   * @returns {Object} Updated subject.
240   */
241
242  const setAtKeyPath = (subject, keyPath, value) => {
243    const segments = keyPathToSegments(keyPath)
244    segments.reduce((prevSubject, key, idx) => {
245      if (typeof prevSubject[key] === 'undefined') {
246        prevSubject[key] = {}
247      }
248
249      if (idx === segments.length - 1) {
250        prevSubject[key] = value
251      }
252
253      return prevSubject[key]
254    }, subject)
255    return subject
256  }
257  /**
258   * Given a dictionary of (nested) keys to function, flatten them to a map
259   * from key path to function.
260   *
261   * @param {Object} methods The (potentially nested) object to serialize.
262   * @param {string} prefix A string with which to prefix entries. Typically not intended to be used by consumers.
263   * @returns {Object} An map from key path in `methods` to functions.
264   */
265
266  const serializeMethods = (methods, prefix) => {
267    const flattenedMethods = {}
268    Object.keys(methods).forEach((key) => {
269      const value = methods[key]
270      const keyPath = createKeyPath(key, prefix)
271
272      if (typeof value === 'object') {
273        // Recurse into any nested children.
274        Object.assign(flattenedMethods, serializeMethods(value, keyPath))
275      }
276
277      if (typeof value === 'function') {
278        // If we've found a method, expose it.
279        flattenedMethods[keyPath] = value
280      }
281    })
282    return flattenedMethods
283  }
284  /**
285   * Given a map of key paths to functions, unpack the key paths to an object.
286   *
287   * @param {Object} flattenedMethods A map of key paths to functions to unpack.
288   * @returns {Object} A (potentially nested) map of functions.
289   */
290
291  const deserializeMethods = (flattenedMethods) => {
292    const methods = {}
293
294    for (const keyPath in flattenedMethods) {
295      setAtKeyPath(methods, keyPath, flattenedMethods[keyPath])
296    }
297
298    return methods
299  }
300
301  /**
302   * Augments an object with methods that match those defined by the remote. When these methods are
303   * called, a "call" message will be sent to the remote, the remote's corresponding method will be
304   * executed, and the method's return value will be returned via a message.
305   * @param {Object} callSender Sender object that should be augmented with methods.
306   * @param {Object} info Information about the local and remote windows.
307   * @param {Array} methodKeyPaths Key paths of methods available to be called on the remote.
308   * @param {Promise} destructionPromise A promise resolved when destroy() is called on the penpal
309   * connection.
310   * @returns {Object} The call sender object with methods that may be called.
311   */
312
313  var connectCallSender = (callSender, info, methodKeyPaths, destroyConnection, log) => {
314    const { localName, local, remote, originForSending, originForReceiving } = info
315    let destroyed = false
316    log(`${localName}: Connecting call sender`)
317
318    const createMethodProxy = (methodName) => {
319      return (...args) => {
320        log(`${localName}: Sending ${methodName}() call`) // This handles the case where the iframe has been removed from the DOM
321        // (and therefore its window closed), the consumer has not yet
322        // called destroy(), and the user calls a method exposed by
323        // the remote. We detect the iframe has been removed and force
324        // a destroy() immediately so that the consumer sees the error saying
325        // the connection has been destroyed. We wrap this check in a try catch
326        // because Edge throws an "Object expected" error when accessing
327        // contentWindow.closed on a contentWindow from an iframe that's been
328        // removed from the DOM.
329
330        let iframeRemoved
331
332        try {
333          if (remote.closed) {
334            iframeRemoved = true
335          }
336        } catch {
337          iframeRemoved = true
338        }
339
340        if (iframeRemoved) {
341          destroyConnection()
342        }
343
344        if (destroyed) {
345          const error = new Error(
346            `Unable to send ${methodName}() call due ` + `to destroyed connection`
347          )
348          error.code = ErrorCode.ConnectionDestroyed
349          throw error
350        }
351
352        return new Promise((resolve, reject) => {
353          const id = generateId()
354
355          const handleMessageEvent = (event) => {
356            if (
357              event.source !== remote ||
358              event.data.penpal !== MessageType.Reply ||
359              event.data.id !== id
360            ) {
361              return
362            }
363
364            if (originForReceiving !== '*' && event.origin !== originForReceiving) {
365              log(
366                `${localName} received message from origin ${event.origin} which did not match expected origin ${originForReceiving}`
367              )
368              return
369            }
370
371            const replyMessage = event.data
372            log(`${localName}: Received ${methodName}() reply`)
373            local.removeEventListener(NativeEventType.Message, handleMessageEvent)
374            let returnValue = replyMessage.returnValue
375
376            if (replyMessage.returnValueIsError) {
377              returnValue = deserializeError(returnValue)
378            }
379
380            ;(replyMessage.resolution === Resolution.Fulfilled ? resolve : reject)(returnValue)
381          }
382
383          local.addEventListener(NativeEventType.Message, handleMessageEvent)
384          const callMessage = {
385            penpal: MessageType.Call,
386            id,
387            methodName,
388            args,
389          }
390          remote.postMessage(callMessage, originForSending)
391        })
392      }
393    } // Wrap each method in a proxy which sends it to the corresponding receiver.
394
395    const flattenedMethods = methodKeyPaths.reduce((api, name) => {
396      api[name] = createMethodProxy(name)
397      return api
398    }, {}) // Unpack the structure of the provided methods object onto the CallSender, exposing
399    // the methods in the same shape they were provided.
400
401    Object.assign(callSender, deserializeMethods(flattenedMethods))
402    return () => {
403      destroyed = true
404    }
405  }
406
407  /**
408   * Handles an ACK handshake message.
409   */
410
411  var handleAckMessageFactory = (
412    serializedMethods,
413    childOrigin,
414    originForSending,
415    destructor,
416    log
417  ) => {
418    const { destroy, onDestroy } = destructor
419    let destroyCallReceiver
420    let receiverMethodNames // We resolve the promise with the call sender. If the child reconnects
421    // (for example, after refreshing or navigating to another page that
422    // uses Penpal, we'll update the call sender with methods that match the
423    // latest provided by the child.
424
425    const callSender = {}
426    return (event) => {
427      if (childOrigin !== '*' && event.origin !== childOrigin) {
428        log(
429          `Parent: Handshake - Received ACK message from origin ${event.origin} which did not match expected origin ${childOrigin}`
430        )
431        return
432      }
433
434      log('Parent: Handshake - Received ACK')
435      const info = {
436        localName: 'Parent',
437        local: window,
438        remote: event.source,
439        originForSending: originForSending,
440        originForReceiving: childOrigin,
441      } // If the child reconnected, we need to destroy the prior call receiver
442      // before setting up a new one.
443
444      if (destroyCallReceiver) {
445        destroyCallReceiver()
446      }
447
448      destroyCallReceiver = connectCallReceiver(info, serializedMethods, log)
449      onDestroy(destroyCallReceiver) // If the child reconnected, we need to remove the methods from the
450      // previous call receiver off the sender.
451
452      if (receiverMethodNames) {
453        receiverMethodNames.forEach((receiverMethodName) => {
454          delete callSender[receiverMethodName]
455        })
456      }
457
458      receiverMethodNames = event.data.methodNames
459      const destroyCallSender = connectCallSender(
460        callSender,
461        info,
462        receiverMethodNames,
463        destroy,
464        log
465      )
466      onDestroy(destroyCallSender)
467      return callSender
468    }
469  }
470
471  /**
472   * Handles a SYN handshake message.
473   */
474
475  var handleSynMessageFactory = (log, serializedMethods, childOrigin, originForSending) => {
476    return (event) => {
477      // Under specific timing circumstances, we can receive an event
478      // whose source is null. This seems to happen when the child iframe is
479      // removed from the DOM about the same time it has sent the SYN event.
480      // https://github.com/Aaronius/penpal/issues/85
481      if (!event.source) {
482        return
483      }
484
485      if (childOrigin !== '*' && event.origin !== childOrigin) {
486        log(
487          `Parent: Handshake - Received SYN message from origin ${event.origin} which did not match expected origin ${childOrigin}`
488        )
489        return
490      }
491
492      log('Parent: Handshake - Received SYN, responding with SYN-ACK')
493      const synAckMessage = {
494        penpal: MessageType.SynAck,
495        methodNames: Object.keys(serializedMethods),
496      }
497      event.source.postMessage(synAckMessage, originForSending)
498    }
499  }
500
501  const CHECK_IFRAME_IN_DOC_INTERVAL = 60000
502  /**
503   * Monitors for iframe removal and destroys connection if iframe
504   * is found to have been removed from DOM. This is to prevent memory
505   * leaks when the iframe is removed from the document and the consumer
506   * hasn't called destroy(). Without this, event listeners attached to
507   * the window would stick around and since the event handlers have a
508   * reference to the iframe in their closures, the iframe would stick
509   * around too.
510   */
511
512  var monitorIframeRemoval = (iframe, destructor) => {
513    const { destroy, onDestroy } = destructor
514    const checkIframeInDocIntervalId = setInterval(() => {
515      if (!iframe.isConnected) {
516        clearInterval(checkIframeInDocIntervalId)
517        destroy()
518      }
519    }, CHECK_IFRAME_IN_DOC_INTERVAL)
520    onDestroy(() => {
521      clearInterval(checkIframeInDocIntervalId)
522    })
523  }
524
525  /**
526   * Starts a timeout and calls the callback with an error
527   * if the timeout completes before the stop function is called.
528   */
529
530  var startConnectionTimeout = (timeout, callback) => {
531    let timeoutId
532
533    if (timeout !== undefined) {
534      timeoutId = window.setTimeout(() => {
535        const error = new Error(`Connection timed out after ${timeout}ms`)
536        error.code = ErrorCode.ConnectionTimeout
537        callback(error)
538      }, timeout)
539    }
540
541    return () => {
542      clearTimeout(timeoutId)
543    }
544  }
545
546  var validateIframeHasSrcOrSrcDoc = (iframe) => {
547    if (!iframe.src && !iframe.srcdoc) {
548      const error = new Error('Iframe must have src or srcdoc property defined.')
549      error.code = ErrorCode.NoIframeSrc
550      throw error
551    }
552  }
553
554  /**
555   * Attempts to establish communication with an iframe.
556   */
557
558  var connectToChild = (options) => {
559    let { iframe, methods = {}, childOrigin, timeout, debug = false } = options
560    const log = createLogger(debug)
561    const destructor = createDestructor('Parent', log)
562    const { onDestroy, destroy } = destructor
563
564    if (!childOrigin) {
565      validateIframeHasSrcOrSrcDoc(iframe)
566      childOrigin = getOriginFromSrc(iframe.src)
567    } // If event.origin is "null", the remote protocol is file: or data: and we
568    // must post messages with "*" as targetOrigin when sending messages.
569    // https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#Using_window.postMessage_in_extensions
570
571    const originForSending = childOrigin === 'null' ? '*' : childOrigin
572    const serializedMethods = serializeMethods(methods)
573    const handleSynMessage = handleSynMessageFactory(
574      log,
575      serializedMethods,
576      childOrigin,
577      originForSending
578    )
579    const handleAckMessage = handleAckMessageFactory(
580      serializedMethods,
581      childOrigin,
582      originForSending,
583      destructor,
584      log
585    )
586    const promise = new Promise((resolve, reject) => {
587      const stopConnectionTimeout = startConnectionTimeout(timeout, destroy)
588
589      const handleMessage = (event) => {
590        if (event.source !== iframe.contentWindow || !event.data) {
591          return
592        }
593
594        if (event.data.penpal === MessageType.Syn) {
595          handleSynMessage(event)
596          return
597        }
598
599        if (event.data.penpal === MessageType.Ack) {
600          const callSender = handleAckMessage(event)
601
602          if (callSender) {
603            stopConnectionTimeout()
604            resolve(callSender)
605          }
606
607          return
608        }
609      }
610
611      window.addEventListener(NativeEventType.Message, handleMessage)
612      log('Parent: Awaiting handshake')
613      monitorIframeRemoval(iframe, destructor)
614      onDestroy((error) => {
615        window.removeEventListener(NativeEventType.Message, handleMessage)
616
617        if (error) {
618          reject(error)
619        }
620      })
621    })
622    return {
623      promise,
624
625      destroy() {
626        // Don't allow consumer to pass an error into destroy.
627        destroy()
628      },
629    }
630  }
631
632  /**
633   * Handles a SYN-ACK handshake message.
634   */
635
636  var handleSynAckMessageFactory = (parentOrigin, serializedMethods, destructor, log) => {
637    const { destroy, onDestroy } = destructor
638    return (event) => {
639      let originQualifies =
640        parentOrigin instanceof RegExp
641          ? parentOrigin.test(event.origin)
642          : parentOrigin === '*' || parentOrigin === event.origin
643
644      if (!originQualifies) {
645        log(
646          `Child: Handshake - Received SYN-ACK from origin ${event.origin} which did not match expected origin ${parentOrigin}`
647        )
648        return
649      }
650
651      log('Child: Handshake - Received SYN-ACK, responding with ACK') // If event.origin is "null", the remote protocol is file: or data: and we
652      // must post messages with "*" as targetOrigin when sending messages.
653      // https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#Using_window.postMessage_in_extensions
654
655      const originForSending = event.origin === 'null' ? '*' : event.origin
656      const ackMessage = {
657        penpal: MessageType.Ack,
658        methodNames: Object.keys(serializedMethods),
659      }
660      window.parent.postMessage(ackMessage, originForSending)
661      const info = {
662        localName: 'Child',
663        local: window,
664        remote: window.parent,
665        originForSending,
666        originForReceiving: event.origin,
667      }
668      const destroyCallReceiver = connectCallReceiver(info, serializedMethods, log)
669      onDestroy(destroyCallReceiver)
670      const callSender = {}
671      const destroyCallSender = connectCallSender(
672        callSender,
673        info,
674        event.data.methodNames,
675        destroy,
676        log
677      )
678      onDestroy(destroyCallSender)
679      return callSender
680    }
681  }
682
683  const areGlobalsAccessible = () => {
684    try {
685      clearTimeout()
686    } catch {
687      return false
688    }
689
690    return true
691  }
692  /**
693   * Attempts to establish communication with the parent window.
694   */
695
696  var connectToParent = (options = {}) => {
697    const { parentOrigin = '*', methods = {}, timeout, debug = false } = options
698    const log = createLogger(debug)
699    const destructor = createDestructor('Child', log)
700    const { destroy, onDestroy } = destructor
701    const serializedMethods = serializeMethods(methods)
702    const handleSynAckMessage = handleSynAckMessageFactory(
703      parentOrigin,
704      serializedMethods,
705      destructor,
706      log
707    )
708
709    const sendSynMessage = () => {
710      log('Child: Handshake - Sending SYN')
711      const synMessage = {
712        penpal: MessageType.Syn,
713      }
714      const parentOriginForSyn = parentOrigin instanceof RegExp ? '*' : parentOrigin
715      window.parent.postMessage(synMessage, parentOriginForSyn)
716    }
717
718    const promise = new Promise((resolve, reject) => {
719      const stopConnectionTimeout = startConnectionTimeout(timeout, destroy)
720
721      const handleMessage = (event) => {
722        // Under niche scenarios, we get into this function after
723        // the iframe has been removed from the DOM. In Edge, this
724        // results in "Object expected" errors being thrown when we
725        // try to access properties on window (global properties).
726        // For this reason, we try to access a global up front (clearTimeout)
727        // and if it fails we can assume the iframe has been removed
728        // and we ignore the message event.
729        if (!areGlobalsAccessible()) {
730          return
731        }
732
733        if (event.source !== parent || !event.data) {
734          return
735        }
736
737        if (event.data.penpal === MessageType.SynAck) {
738          const callSender = handleSynAckMessage(event)
739
740          if (callSender) {
741            window.removeEventListener(NativeEventType.Message, handleMessage)
742            stopConnectionTimeout()
743            resolve(callSender)
744          }
745        }
746      }
747
748      window.addEventListener(NativeEventType.Message, handleMessage)
749      sendSynMessage()
750      onDestroy((error) => {
751        window.removeEventListener(NativeEventType.Message, handleMessage)
752
753        if (error) {
754          reject(error)
755        }
756      })
757    })
758    return {
759      promise,
760
761      destroy() {
762        // Don't allow consumer to pass an error into destroy.
763        destroy()
764      },
765    }
766  }
767
768  var indexForBundle = {
769    connectToChild,
770    connectToParent,
771    ErrorCode,
772  }
773
774  return indexForBundle
775})()
776
777let resizeObserver = null
778
779const PROPERTY_CONTAINER = 'aryeo_property_container'
780const PROPERTY_CONTAINER_IFRAME = 'aryeo_property_iframe'
781const PROPERTY_CONTAINER_BUTTON = 'aryeo_property_button'
782
783class Aryeo {
784  constructor(el) {
785    if (!el || el.dataset.aryeoInitialized) {
786      return
787    }
788
789    this.el = el
790    this.container = null
791    this.containerButton = null
792    this.propertyWebsiteIframe = null
793
794    this.IFRAME_URL = 'https://listing-embed.aryeo.com'
795    this.ACCESS_TOKEN = 'nEoDsFf0QR15Gu1FRfYzyAyJuslCHYfRqu6fFC2n'
796    this.HASH_ID = 'WLEJEV'
797    this.GROUP_ID = 'cf30ccea-588b-4b7d-8655-a2e5d251557a'
798    this.PARAMS = JSON.parse('{\"showBathFilter\":true,\"showBedFilter\":true,\"showPriceFilter\":true,\"showToolbar\":true,\"showStatusFilter\":false,\"statusFilterOption\":null}')
799
800    this.BANNER_HTML = '\"<div class=\\\"aryeo-listing-embed\\\">\\n  <div class=\\\"ar-relative ar-bg-black ar-flex ar-items-center ar-justify-between\\\">\\n    <button class=\\\"aryeo_property_button ar-flex ar-items-center ar-justify-center ar-p-5 ar-rounded-md ar-p-3 ar-hover:bg-primary-light ar-focus:outline-none ar-focus:ring-white ar-border-0 ar-text-white\\\">\\n      <svg class=\\\"ar-w-6 ar-h-6 ar-mr-3\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" viewBox=\\\"0 0 24 24\\\" xmlns=\\\"http:\\/\\/www.w3.org\\/2000\\/svg\\\">\\n        <path stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M10 19l-7-7m0 0l7-7m-7 7h18\\\"><\\/path>\\n      <\\/svg>\\n      Back\\n    <\\/button>\\n    <button class=\\\"aryeo_property_button ar-flex ar-items-center ar-justify-center ar-p-5 ar-rounded-md ar-p-3 ar-hover:bg-primary-light ar-focus:outline-none ar-focus:ring-white ar-border-0 ar-text-white\\\">
800\\n      <span class=\\\"ar-sr-only\\\">Dismiss<\\/span>\\n      <svg class=\\\"ar-h-6 ar-w-6\\\" xmlns=\\\"http:\\/\\/www.w3.org\\/2000\\/svg\\\" fill=\\\"currentColor\\\" viewBox=\\\"0 0 24 24\\\" stroke=\\\"currentColor\\\" aria-hidden=\\\"true\\\">\\n        <path stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M6 18L18 6M6 6l12 12\\\" \\/>\\n      <\\/svg>\\n    <\\/button>\\n  <\\/div>\\n<\\/div>\"'
801    this.LOADER_HTML = '\"<div class=\\\"aryeo-listing-embed\\\">\\n  <div class=\\\"ar-w-full ar-h-screen ar-flex ar-items-center ar-justify-center\\\">\\n    <svg viewBox=\\\"0 0 38 38\\\" xmlns=\\\"http:\\/\\/www.w3.org\\/2000\\/svg\\\" stroke=\\\"currentColor\\\" class=\\\"ar-text-white ar-w-10 ar-h-10 ar-transition-all ar-ease-in-out ar-duration-500\\\">\\n      <g fill=\\\"none\\\" fill-rule=\\\"evenodd\\\">\\n        <g transform=\\\"translate(1 1)\\\" stroke-width=\\\"2\\\">\\n          <circle stroke-opacity=\\\".5\\\" cx=\\\"18\\\" cy=\\\"18\\\" r=\\\"18\\\" \\/>\\n          <path d=\\\"M36 18c0-9.94-8.06-18-18-18\\\">\\n            <animateTransform attributeName=\\\"transform\\\" type=\\\"rotate\\\" from=\\\"0 18 18\\\" to=\\\"360 18 18\\\" dur=\\\"1s\\\" repeatCount=\\\"indefinite\\\" \\/>\\n          <\\/path>\\n        <\\/g>\\n      <\\/g>\\n    <\\/svg>\\n  <\\/div>\\n<\\/div>\"'
802
803    const style = document.createElement('link')
804    style.type = 'text/css'
805    style.rel = 'stylesheet'
806    style.href = 'https://cdn.aryeo.com/build/assets/embed-nzvZPOh6.css'
807    style.setAttribute('id', 'aryeo_css')
808
809    document.head.appendChild(style)
810
811    this.setIframe()
812    el.dataset.aryeoInitialized = true
813  }
814
815  static create(el) {
816    return new Aryeo(el)
817  }
818
819  async setIframe() {
820    const params = this.buildParams()
821
822    const iframe = document.createElement('iframe')
823
824    iframe.src = `${this.IFRAME_URL}?${params}`
825
826    iframe.style = `border: none; width: 100%; height: 100%; overflow-y:hidden;`
827
828    iframe.setAttribute('id', this.HASH_ID)
829    iframe.title = 'Aryeo'
830    iframe.name = `aryeo_listing_iframe ${this.HASH_ID}`
831    iframe.setAttribute('scrolling', 'no')
832
833    const container = document.createElement('div')
834
835    container.appendChild(iframe)
836
837    this.el.parentNode.replaceChild(container, this.el)
838    this.setConnection(iframe)
839  }
840
841  displayPropertyIframe({ url, id }) {
842    if (document.getElementById(PROPERTY_CONTAINER)) return
843    if (!this.container) {
844      this.container = document.createElement('div')
845      this.container.setAttribute('id', PROPERTY_CONTAINER)
846      this.container.style.position = 'fixed'
847      this.container.style.top = '0'
848      this.container.style.left = '0'
849      this.container.style.width = '100%'
850      this.container.style.height = '100vh'
851      this.container.style.background = 'rgba(0,0,0, 0.75)'
852      this.container.style.zIndex = '99998'
853      this.container.style.overflowY = 'scroll'
854      this.container.style.display = 'flex'
855      this.container.style.flexDirection = 'column'
856      this.container.onclick = this.removeDisplayPropertyIframe
857
858      this.banner = document.createElement('div')
859      this.banner.innerHTML = JSON.parse(this.BANNER_HTML)
860
861      this.loader = document.createElement('div')
862      this.loader.innerHTML = JSON.parse(this.LOADER_HTML)
863
864      this.propertyWebsiteIframe = document.createElement('iframe')
865      this.propertyWebsiteIframe.setAttribute('id', PROPERTY_CONTAINER_IFRAME)
866      this.propertyWebsiteIframe.setAttribute('loading', 'lazy')
867      this.propertyWebsiteIframe.style.margin = '0 auto'
868      this.propertyWebsiteIframe.style.height = '100%'
869      this.propertyWebsiteIframe.style.border = 'none'
870      this.propertyWebsiteIframe.allowFullscreen = true
871
872      this.container.appendChild(this.banner)
873      this.container.appendChild(this.propertyWebsiteIframe)
874      this.container.appendChild(this.loader)
875    }
876
877    this.loader.style.display = 'block'
878    this.propertyWebsiteIframe.style.display = 'none'
879
880    document.body.style.overflowY = 'hidden'
881
882    this.propertyWebsiteIframe.style.transition = 'all 1s ease-out'
883
884    this.propertyWebsiteIframe.src = url
885    this.setPropertyConnection(this.propertyWebsiteIframe)
886
887    document.body.appendChild(this.container)
888
889    document.querySelectorAll(`.${PROPERTY_CONTAINER_BUTTON}`).forEach((el) => {
890      el.onclick = this.removeDisplayPropertyIframe
891    })
892
893    setTimeout(() => {
894      this.loader.style.display = 'none'
895      this.propertyWebsiteIframe.style.display = 'block'
896
897      resizeObserver = new ResizeObserver(this.resizeContainer)
898      resizeObserver.observe(this.container)
899    }, 1500)
900
901    history.pushState({ listing: 1 }, 'Aryeo Property website', `?listing=${id}`)
902  }
903
904  resizeContainer() {
905    document.getElementById(PROPERTY_CONTAINER_IFRAME).style.width =
906      window.innerWidth > 960 ? '80%' : '100%'
907  }
908
909  removeDisplayPropertyIframe() {
910    history.pushState({}, document.title, location.pathname)
911    resizeObserver.disconnect()
912    document.body.removeChild(document.getElementById(PROPERTY_CONTAINER))
913    document.body.style.overflowY = 'auto'
914  }
915
916  buildParams() {
917    const params = new URLSearchParams({
918      ...this.PARAMS,
919      agentId: '',
920      listing: new URL(document.location).searchParams.get('listing'),
921      groupId: 'cf30ccea-588b-4b7d-8655-a2e5d251557a',
922      accessToken: 'nEoDsFf0QR15Gu1FRfYzyAyJuslCHYfRqu6fFC2n',
923    })
924
925    return params.toString()
926  }
927
928  setPropertyConnection(iframe) {
929    Penpal.connectToChild({
930      // The iframe to which a connection should be made
931      iframe,
932      // Methods the parent is exposing to the child
933      methods: {
934        resize(height) {
935          iframe.style.height = `${height}px`
936        },
937      },
938    })
939  }
940
941  setConnection(element) {
942    const _self = this
943    const iframe = element
944    Penpal.connectToChild({
945      // The iframe to which a connection should be made
946      iframe,
947      // Methods the parent is exposing to the child
948      methods: {
949        resize(height) {
950          iframe.style.height = `${height + 30}px`
951        },
952        showProperty(url) {
953          _self.displayPropertyIframe(url)
954        },
955      },
956    })
957  }
958}
959
960window.addEventListener('load', function () {
961  Aryeo.create(document.getElementById('WLEJEV'))
962})

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.