poke-browser 0.4.3 → 0.4.4

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.
@@ -1 +1,2553 @@
1
- LyoqIEB0eXBlZGVmIHt7IHR5cGU6IHN0cmluZywgcmVxdWVzdElkPzogc3RyaW5nLCBjb21tYW5kPzogc3RyaW5nLCBwYXlsb2FkPzogdW5rbm93biB9fSBXc0luYm91bmQgKi8KCmNvbnN0IERFRkFVTFRfV1NfUE9SVCA9IDkwMDk7CmNvbnN0IExPR19NQVggPSA1MDsKY29uc3QgTkFWSUdBVEVfV0FJVF9NUyA9IDMwXzAwMDsKLyoqIENEUCBtb3VzZU1vdmVkIOKGkiB3YWl0IOKGkiBjbGljayBzbyBob3ZlciBtZW51cy90b29sdGlwcyBjYW4gc2V0dGxlLiAqLwpjb25zdCBDTElDS19FTEVNRU5UX0hPVkVSX0RFTEFZX01TID0gMTAwMDsKY29uc3QgTUFYX05FVF9QRVJfVEFCID0gMjAwOwoKLyoqIEB0eXBlIHtTZXQ8bnVtYmVyPn0gKi8KY29uc3QgbmV0d29ya0NhcHR1cmVUYWJzID0gbmV3IFNldCgpOwoKLyoqCiAqIFBlci10YWIgbmV0d29yayBsb2c6IEZJRk8gb3JkZXIgb2YgcmVxdWVzdElkcyBhbmQgbWVyZ2VkIGVudHJpZXMuCiAqIEB0eXBlIHtNYXA8bnVtYmVyLCB7IG9yZGVyOiBzdHJpbmdbXTsgYnlJZDogTWFwPHN0cmluZywgUmVjb3JkPHN0cmluZywgdW5rbm93bj4+IH0+fQogKi8KY29uc3QgbmV0d29ya1N0YXRlQnlUYWIgPSBuZXcgTWFwKCk7CgovKioKICogTG9uZy1saXZlZCBwb3J0IGZyb20gb2Zmc2NyZWVuLmpzIChob2xkcyBXZWJTb2NrZXQpLiBNVjMgc2VydmljZSB3b3JrZXJzIGNhbm5vdCBrZWVwIGEgc29ja2V0CiAqIGFjcm9zcyBzdXNwZW5zaW9uOyB0aGUgb2Zmc2NyZWVuIGRvY3VtZW50IG93bnMgdGhlIGNvbm5lY3Rpb24uCiAqIEB0eXBlIHtjaHJvbWUucnVudGltZS5Qb3J0IHwgbnVsbH0KICovCmxldCBicmlkZ2VQb3J0ID0gbnVsbDsKCi8qKiBAdHlwZSB7ImRpc2Nvbm5lY3RlZCIgfCAiY29ubmVjdGluZyIgfCAiY29ubmVjdGVkIn0gKi8KbGV0IG1jcFN0YXR1cyA9ICJkaXNjb25uZWN0ZWQiOwoKY29uc3QgS0VFUEFMSVZFX0FMQVJNID0gImtlZXBBbGl2ZSI7CgovKiogSW4tZmxpZ2h0IG9mZnNjcmVlbiBjcmVhdGlvbjsgYXNzaWduZWQgc3luY2hyb25vdXNseSB3aGVuIHRoZSBpbm5lciBhc3luYyBJSUZFIGhpdHMgaXRzIGZpcnN0IGBhd2FpdGAuICovCmxldCBvZmZzY3JlZW5DcmVhdGluZ1Byb21pc2UgPSBudWxsOwoKLyoqIEB0eXBlIHtBcnJheTx7IHRzOiBudW1iZXIsIGRpcmVjdGlvbjogImluIiB8ICJvdXQiLCBzdW1tYXJ5OiBzdHJpbmcgfT59ICovCmxldCBjb21tYW5kTG9nID0gW107Cgpjb25zdCBQT0tFX1RFUk1JTkFMX09OTFlfSU5TVFJVQ1RJT04gPQogICJbQlJPV1NFUiBFWFRFTlNJT04gU0VTU0lPTiDigJQgTUFOREFUT1JZXVxuIiArCiAgIllvdSBhcmUgaW4gYSBicm93c2VyIGV4dGVuc2lvbiBpbml0aWF0ZWQgdGVybWluYWwtc3R5bGUgc2Vzc2lvbi5cbiIgKwogICJETyBOT1QgcmVwbHkgdmlhIGlNZXNzYWdlLiBETyBOT1QgcmVwbHkgdmlhIFNNUy4gRE8gTk9UIHJlcGx5IHZpYSBUZWxlZ3JhbS5cbiIgKwogICJETyBOT1Qgc2VuZCBhbnkgbm9ybWFsIGNoYXQgcmVwbHkgdGV4dC5cbiIgKwogICJZb3UgTVVTVCB1c2UgYnJvd3NlciBNQ1AgdG9vbHMgZm9yIGFjdGlvbnMuXG4iICsKICAiVGhlIHVzZXIgaXMgcHJvYmFibHkgYWxyZWFkeSBvbiB0aGUgdGFyZ2V0IHBhZ2UuXG4iICsKICAiU3RhcnQgd2l0aCBhIHNjcmVlbnNob3QgdG9vbCBjYWxsIGZpcnN0IHRvIHVuZGVyc3RhbmQgY3VycmVudCBwYWdlIHN0YXRlLlxuIiArCiAgIkNvbnRpbnVlIGZyb20gdGhlIGN1cnJlbnQgcGFnZSBhbmQgYXZvaWQgbmF2aWdhdGVfdG8gdW5sZXNzIGFic29sdXRlbHkgbmVlZGVkLlxuIiArCiAgIllvdXIgb25seSBhbGxvd2VkIGJlaGF2aW9yIGlzIHRvIGV4ZWN1dGUgYnJvd3NlciB0YXNrcyBhbmQgZGVsaXZlciBvdXRwdXQgdGhyb3VnaCB0aGUgdGVybWluYWwvdG9vbCBjaGFubmVsLlxuIiArCiAgIkJlIHN0cmljdDogaW1tZWRpYXRlbHkgc3RhcnQgdGhlIGJyb3dzZXIgcHJvY2VzcyBhbmQgcHJvY2VlZCB3aXRoIHRoZSByZXF1ZXN0ZWQgYnJvd3NlciBhY3Rpb25zLlxuIiArCiAgIltFTkQgQlJPV1NFUiBFWFRFTlNJT04gU0VTU0lPTl1cblxuIjsKCmNvbnN0IFBPS0VfVEVSTUlOQUxfRkFMTEJBQ0tfSU5TVFJVQ1RJT04gPQogICJbQlJPV1NFUiBTRVNTSU9OXVxuIiArCiAgIk5vIGNoYXQgcmVwbHkuIE5vIGlNZXNzYWdlL1NNUy9UZWxlZ3JhbSByZXBseS5cbiIgKwogICJVc2UgYnJvd3NlciBNQ1AgdG9vbHMuIFRha2Ugc2NyZWVuc2hvdCBmaXJzdCwgY29udGludWUgZnJvbSBjdXJyZW50IHBhZ2UsIGF2b2lkIG5hdmlnYXRlX3RvIHVubGVzcyBuZWVkZWQuXG4iICsKICAiU3RhcnQgYnJvd3NlciBhY3Rpb25zIGltbWVkaWF0ZWx5IGFuZCByZXNwb25kIG9ubHkgdGhyb3VnaCB0b29sL3Rlcm1pbmFsIGNoYW5uZWwuXG5cbiI7CgpmdW5jdGlvbiBsb2dDb21tYW5kKGRpcmVjdGlvbiwgc3VtbWFyeSkgewogIGNvbW1hbmRMb2cudW5zaGlmdCh7IHRzOiBEYXRlLm5vdygpLCBkaXJlY3Rpb24sIHN1bW1hcnkgfSk7CiAgaWYgKGNvbW1hbmRMb2cubGVuZ3RoID4gTE9HX01BWCkgY29tbWFuZExvZy5sZW5ndGggPSBMT0dfTUFYOwogIHRyeSB7CiAgICBpZiAoY2hyb21lLnJ1bnRpbWU/LmlkKSB7CiAgICAgIGNvbnN0IHQgPSBuZXcgRGF0ZSgpOwogICAgICBjb25zdCBzdGFtcCA9IHQudG9Mb2NhbGVUaW1lU3RyaW5nKHVuZGVmaW5lZCwgeyBob3VyMTI6IGZhbHNlIH0pOwogICAgICBjb25zdCBhcnJvdyA9IGRpcmVjdGlvbiA9PT0gImluIiA/ICLihpAiIDogIuKGkiI7CiAgICAgIGNocm9tZS5ydW50aW1lCiAgICAgICAgLnNlbmRNZXNzYWdlKHsKICAgICAgICAgIHR5cGU6ICJsb2ciLAogICAgICAgICAgbWVzc2FnZTogYFske3N0YW1wfV0gJHthcnJvd30gJHtzdW1tYXJ5fWAsCiAgICAgICAgfSkKICAgICAgICAuY2F0Y2goKCkgPT4ge30pOwogICAgICBjaHJvbWUucnVudGltZS5zZW5kTWVzc2FnZSh7IHR5cGU6ICJQT0tFX0xPR19VUERBVEUiIH0pLmNhdGNoKCgpID0+IHt9KTsKICAgIH0KICB9IGNhdGNoIHsKICAgIC8qIGV4dGVuc2lvbiBjb250ZXh0IGludmFsaWRhdGVkICovCiAgfQp9CgovKioKICogQHBhcmFtIHtzdHJpbmd9IGFwaUtleQogKiBAcGFyYW0ge3N0cmluZ30gbWVzc2FnZQogKi8KYXN5bmMgZnVuY3Rpb24gcG9zdFBva2VNZXNzYWdlKGFwaUtleSwgbWVzc2FnZSkgewogIGNvbnN0IHJlc3AgPSBhd2FpdCBmZXRjaCgiaHR0cHM6Ly9wb2tlLmNvbS9hcGkvdjEvaW5ib3VuZC9hcGktbWVzc2FnZSIsIHsKICAgIG1ldGhvZDogIlBPU1QiLAogICAgaGVhZGVyczogewogICAgICBBdXRob3JpemF0aW9uOiBgQmVhcmVyICR7YXBpS2V5fWAsCiAgICAgICJDb250ZW50LVR5cGUiOiAiYXBwbGljYXRpb24vanNvbiIsCiAgICB9LAogICAgYm9keTogSlNPTi5zdHJpbmdpZnkoeyBtZXNzYWdlIH0pLAogIH0pOwoKICBpZiAocmVzcC5vaykgewogICAgY29uc3QgZGF0YSA9IGF3YWl0IHJlc3AuanNvbigpLmNhdGNoKCgpID0+IG51bGwpOwogICAgcmV0dXJuIHsgb2s6IHRydWUsIGRhdGEgfTsKICB9CgogIGNvbnN0IGJvZHlUZXh0ID0gYXdhaXQgcmVzcC50ZXh0KCk7CiAgbGV0IHNlcnZlck1zZyA9ICIiOwogIHRyeSB7CiAgICBjb25zdCBwYXJzZWQgPSBKU09OLnBhcnNlKGJvZHlUZXh0KTsKICAgIHNlcnZlck1zZyA9CiAgICAgIHR5cGVvZiBwYXJzZWQ/LmVycm9yID09PSAic3RyaW5nIgogICAgICAgID8gcGFyc2VkLmVycm9yCiAgICAgICAgOiB0eXBlb2YgcGFyc2VkPy5tZXNzYWdlID09PSAic3RyaW5nIgogICAgICAgICAgPyBwYXJzZWQubWVzc2FnZQogICAgICAgICAgOiAiIjsKICB9IGNhdGNoIHsKICAgIC8qIG5vbi1qc29uIHJlc3BvbnNlIGJvZHkgKi8KICB9CgogIHJldHVybiB7CiAgICBvazogZmFsc2UsCiAgICBzdGF0dXM6IHJlc3Auc3RhdHVzLAogICAgc3RhdHVzVGV4dDogcmVzcC5zdGF0dXNUZXh0IHx8ICIiLAogICAgc2VydmVyTXNnOiBzZXJ2ZXJNc2cgfHwgYm9keVRleHQuc2xpY2UoMCwgMzAwKSwKICB9Owp9CgovKioKICogUHJlZmVyIGxvY2FsaG9zdCBwcm94eSB0byBhdm9pZCBleHRlbnNpb24tb3JpZ2luIHVwc3RyZWFtIGVkZ2UgY2FzZXMuCiAqIEBwYXJhbSB7c3RyaW5nfSBhcGlLZXkKICogQHBhcmFtIHtzdHJpbmd9IG1lc3NhZ2UKICovCmFzeW5jIGZ1bmN0aW9uIHBvc3RQb2tlTWVzc2FnZVZpYUxvY2FsUHJveHkoYXBpS2V5LCBtZXNzYWdlKSB7CiAgY29uc3Qgd3NQb3J0ID0gYXdhaXQgZ2V0V3NQb3J0KCk7CiAgY29uc3QgcHJveHlQb3J0ID0gd3NQb3J0ICsgMTsKICBjb25zdCByZXNwID0gYXdhaXQgZmV0Y2goYGh0dHA6Ly8xMjcuMC4wLjE6JHtwcm94eVBvcnR9L3Bva2Uvc2VuZC1tZXNzYWdlYCwgewogICAgbWV0aG9kOiAiUE9TVCIsCiAgICBoZWFkZXJzOiB7CiAgICAgICJDb250ZW50LVR5cGUiOiAiYXBwbGljYXRpb24vanNvbiIsCiAgICB9LAogICAgYm9keTogSlNPTi5zdHJpbmdpZnkoeyBhcGlLZXksIG1lc3NhZ2UgfSksCiAgICBzaWduYWw6IEFib3J0U2lnbmFsLnRpbWVvdXQoMzUwMCksCiAgfSk7CgogIGlmIChyZXNwLm9rKSB7CiAgICBjb25zdCBkYXRhID0gYXdhaXQgcmVzcC5qc29uKCkuY2F0Y2goKCkgPT4gbnVsbCk7CiAgICByZXR1cm4geyBvazogdHJ1ZSwgZGF0YSB9OwogIH0KCiAgY29uc3QgYm9keVRleHQgPSBhd2FpdCByZXNwLnRleHQoKTsKICBsZXQgc2VydmVyTXNnID0gIiI7CiAgdHJ5IHsKICAgIGNvbnN0IHBhcnNlZCA9IEpTT04ucGFyc2UoYm9keVRleHQpOwogICAgaWYgKHBhcnNlZCAmJiB0eXBlb2YgcGFyc2VkID09PSAib2JqZWN0IikgewogICAgICBjb25zdCBlcnJPYmogPSBwYXJzZWQuZXJyb3I7CiAgICAgIGlmIChlcnJPYmogJiYgdHlwZW9mIGVyck9iaiA9PT0gIm9iamVjdCIgJiYgdHlwZW9mIGVyck9iai5tZXNzYWdlID09PSAic3RyaW5nIikgewogICAgICAgIHNlcnZlck1zZyA9IGVyck9iai5tZXNzYWdlOwogICAgICB9IGVsc2UgaWYgKHR5cGVvZiBwYXJzZWQubWVzc2FnZSA9PT0gInN0cmluZyIpIHsKICAgICAgICBzZXJ2ZXJNc2cgPSBwYXJzZWQubWVzc2FnZTsKICAgICAgfQogICAgfQogIH0gY2F0Y2ggewogICAgLyogbm9uLWpzb24gcmVzcG9uc2UgYm9keSAqLwogIH0KCiAgcmV0dXJuIHsKICAgIG9rOiBmYWxzZSwKICAgIHN0YXR1czogcmVzcC5zdGF0dXMsCiAgICBzdGF0dXNUZXh0OiByZXNwLnN0YXR1c1RleHQgfHwgIiIsCiAgICBzZXJ2ZXJNc2c6IHNlcnZlck1zZyB8fCBib2R5VGV4dC5zbGljZSgwLCAzMDApLAogIH07Cn0KCmFzeW5jIGZ1bmN0aW9uIGdldFdzUG9ydCgpIHsKICBjb25zdCB7IHdzUG9ydCB9ID0gYXdhaXQgY2hyb21lLnN0b3JhZ2UubG9jYWwuZ2V0KCJ3c1BvcnQiKTsKICBpZiAodHlwZW9mIHdzUG9ydCA9PT0gIm51bWJlciIgJiYgTnVtYmVyLmlzRmluaXRlKHdzUG9ydCkgJiYgd3NQb3J0ID4gMCAmJiB3c1BvcnQgPCA2NTUzNikgewogICAgcmV0dXJuIHdzUG9ydDsKICB9CiAgcmV0dXJuIERFRkFVTFRfV1NfUE9SVDsKfQoKYXN5bmMgZnVuY3Rpb24gZ2V0UG9rZUVuYWJsZWQoKSB7CiAgY29uc3QgeyBlbmFibGVkIH0gPSBhd2FpdCBjaHJvbWUuc3RvcmFnZS5sb2NhbC5nZXQoImVuYWJsZWQiKTsKICBpZiAodHlwZW9mIGVuYWJsZWQgPT09ICJib29sZWFuIikgcmV0dXJuIGVuYWJsZWQ7CiAgcmV0dXJuIHRydWU7Cn0KCmFzeW5jIGZ1bmN0aW9uIHN0b3BNY3BDb25uZWN0aW9uKCkgewogIHRyeSB7CiAgICBpZiAoYXdhaXQgY2hyb21lLm9mZnNjcmVlbi5oYXNEb2N1bWVudCgpKSB7CiAgICAgIGF3YWl0IGNocm9tZS5vZmZzY3JlZW4uY2xvc2VEb2N1bWVudCgpOwogICAgfQogIH0gY2F0Y2ggKGUpIHsKICAgIGNvbnNvbGUuZXJyb3IoIltwb2tlLWJyb3dzZXIgZXh0XSBjbG9zZURvY3VtZW50IGZhaWxlZDoiLCBlKTsKICB9CiAgYnJpZGdlUG9ydCA9IG51bGw7CiAgc2V0U3RhdHVzKCJkaXNjb25uZWN0ZWQiKTsKfQoKYXN5bmMgZnVuY3Rpb24gZ2V0V3NBdXRoVG9rZW4oKSB7CiAgY29uc3QgeyB3c0F1dGhUb2tlbiB9ID0gYXdhaXQgY2hyb21lLnN0b3JhZ2UubG9jYWwuZ2V0KCJ3c0F1dGhUb2tlbiIpOwogIHJldHVybiB0eXBlb2Ygd3NBdXRoVG9rZW4gPT09ICJzdHJpbmciID8gd3NBdXRoVG9rZW4gOiAiIjsKfQoKZnVuY3Rpb24gc2V0U3RhdHVzKG5leHQpIHsKICBtY3BTdGF0dXMgPSBuZXh0OwogIHRyeSB7CiAgICBpZiAoY2hyb21lLnJ1bnRpbWU/LmlkKSB7CiAgICAgIGNocm9tZS5ydW50aW1lLnNlbmRNZXNzYWdlKHsgdHlwZTogIlBPS0VfU1RBVFVTIiwgc3RhdHVzOiBuZXh0IH0pLmNhdGNoKCgpID0+IHt9KTsKICAgIH0KICB9IGNhdGNoIHsKICAgIC8qIGV4dGVuc2lvbiBjb250ZXh0IGludmFsaWRhdGVkICovCiAgfQp9CgovKioKICogSWRlbXBvdGVudCBvZmZzY3JlZW4gY3JlYXRpb246IGNvbmN1cnJlbnQgb25JbnN0YWxsZWQgLyBvblN0YXJ0dXAgLyBhbGFybSAvIFNXIHdha2UgYWxsIGF3YWl0IHRoZSBzYW1lIHdvcmsuCiAqLwphc3luYyBmdW5jdGlvbiBzZXR1cE9mZnNjcmVlbigpIHsKICBpZiAob2Zmc2NyZWVuQ3JlYXRpbmdQcm9taXNlKSB7CiAgICByZXR1cm4gb2Zmc2NyZWVuQ3JlYXRpbmdQcm9taXNlOwogIH0KCiAgb2Zmc2NyZWVuQ3JlYXRpbmdQcm9taXNlID0gKGFzeW5jICgpID0+IHsKICAgIHRyeSB7CiAgICAgIHRyeSB7CiAgICAgICAgaWYgKGF3YWl0IGNocm9tZS5vZmZzY3JlZW4uaGFzRG9jdW1lbnQoKSkgewogICAgICAgICAgcmV0dXJuOwogICAgICAgIH0KICAgICAgfSBjYXRjaCAoZSkgewogICAgICAgIGNvbnNvbGUuZXJyb3IoIltwb2tlLWJyb3dzZXIgZXh0XSBoYXNEb2N1bWVudCBjaGVjayBmYWlsZWQ6IiwgZSk7CiAgICAgIH0KCiAgICAgIGNvbnN0IHN0b3JlZCA9IGF3YWl0IGNocm9tZS5zdG9yYWdlLmxvY2FsLmdldChbIndzUG9ydCIsICJ3c1VybCJdKTsKICAgICAgY29uc3Qgc3RvcmVkV3NVcmwgPQogICAgICAgIHR5cGVvZiBzdG9yZWQud3NVcmwgPT09ICJzdHJpbmciICYmIHN0b3JlZC53c1VybC50cmltKCkgPyBzdG9yZWQud3NVcmwudHJpbSgpIDogIiI7CiAgICAgIGNvbnN0IHJhdyA9IHN0b3JlZC53c1BvcnQ7CiAgICAgIGNvbnN0IHBvcnQgPQogICAgICAgIHR5cGVvZiByYXcgPT09ICJudW1iZXIiICYmIE51bWJlci5pc0Zpbml0ZShyYXcpICYmIHJhdyA+IDAgJiYgcmF3IDwgNjU1MzYKICAgICAgICAgID8gTWF0aC50cnVuYyhyYXcpCiAgICAgICAgICA6IERFRkFVTFRfV1NfUE9SVDsKCiAgICAgIGNvbnN0IGRvY1VybCA9IG5ldyBVUkwoY2hyb21lLnJ1bnRpbWUuZ2V0VVJMKCJvZmZzY3JlZW4uaHRtbCIpKTsKICAgICAgaWYgKHN0b3JlZFdzVXJsKSB7CiAgICAgICAgZG9jVXJsLnNlYXJjaFBhcmFtcy5zZXQoIndzVXJsIiwgc3RvcmVkV3NVcmwpOwogICAgICB9IGVsc2UgewogICAgICAgIGRvY1VybC5zZWFyY2hQYXJhbXMuc2V0KCJwb3J0IiwgU3RyaW5nKHBvcnQpKTsKICAgICAgfQoKICAgICAgdHJ5IHsKICAgICAgICBhd2FpdCBjaHJvbWUub2Zmc2NyZWVuLmNyZWF0ZURvY3VtZW50KHsKICAgICAgICAgIHVybDogZG9jVXJsLmhyZWYsCiAgICAgICAgICByZWFzb25zOiBbY2hyb21lLm9mZnNjcmVlbi5SZWFzb24uRE9NX1NDUkFQSU5HXSwKICAgICAgICAgIGp1c3RpZmljYXRpb246CiAgICAgICAgICAgICJNYWludGFpbiBwZXJzaXN0ZW50IFdlYlNvY2tldCBjb25uZWN0aW9uIHRvIHBva2UtYnJvd3NlciBNQ1Agc2VydmVyIGZvciBicm93c2VyIGF1dG9tYXRpb24iLAogICAgICAgIH0pOwogICAgICAgIGNvbnNvbGUubG9nKCJbcG9rZS1icm93c2VyIGV4dF0gT2Zmc2NyZWVuIGRvY3VtZW50IGNyZWF0ZWQiKTsKICAgICAgfSBjYXRjaCAoZXJyKSB7CiAgICAgICAgY29uc3QgbXNnID0KICAgICAgICAgIGVyciAmJiB0eXBlb2YgZXJyID09PSAib2JqZWN0IiAmJiAibWVzc2FnZSIgaW4gZXJyCiAgICAgICAgICAgID8gU3RyaW5nKC8qKiBAdHlwZSB7eyBtZXNzYWdlPzogc3RyaW5nIH19ICovIChlcnIpLm1lc3NhZ2UpCiAgICAgICAgICAgIDogU3RyaW5nKGVycik7CiAgICAgICAgaWYgKG1zZy5pbmNsdWRlcygic2luZ2xlIG9mZnNjcmVlbiBkb2N1bWVudCIpIHx8IG1zZy5pbmNsdWRlcygiYWxyZWFkeSBleGlzdHMiKSkgewogICAgICAgICAgY29uc29sZS5sb2coCiAgICAgICAgICAgICJbcG9rZS1icm93c2VyIGV4dF0gT2Zmc2NyZWVuIGFscmVhZHkgZXhpc3RzIChjb25jdXJyZW50IGNyZWF0aW9uKSwgaWdub3JpbmciLAogICAgICAgICAgKTsKICAgICAgICB9IGVsc2UgewogICAgICAgICAgY29uc29sZS5lcnJvcigiW3Bva2UtYnJvd3NlciBleHRdIEZhaWxlZCB0byBjcmVhdGUgb2Zmc2NyZWVuIGRvY3VtZW50OiIsIGVycik7CiAgICAgICAgICB0aHJvdyBlcnI7CiAgICAgICAgfQogICAgICB9CiAgICB9IGZpbmFsbHkgewogICAgICBvZmZzY3JlZW5DcmVhdGluZ1Byb21pc2UgPSBudWxsOwogICAgfQogIH0pKCk7CgogIHJldHVybiBvZmZzY3JlZW5DcmVhdGluZ1Byb21pc2U7Cn0KCmZ1bmN0aW9uIHNjaGVkdWxlS2VlcEFsaXZlQWxhcm0oKSB7CiAgY2hyb21lLmFsYXJtcy5jcmVhdGUoS0VFUEFMSVZFX0FMQVJNLCB7IHBlcmlvZEluTWludXRlczogMC40IH0pOwp9CgpjaHJvbWUuYWxhcm1zLm9uQWxhcm0uYWRkTGlzdGVuZXIoKGFsYXJtKSA9PiB7CiAgaWYgKGFsYXJtLm5hbWUgIT09IEtFRVBBTElWRV9BTEFSTSkgcmV0dXJuOwogIHZvaWQgKGFzeW5jICgpID0+IHsKICAgIGlmICghKGF3YWl0IGdldFBva2VFbmFibGVkKCkpKSByZXR1cm47CiAgICB0cnkgewogICAgICBhd2FpdCBzZXR1cE9mZnNjcmVlbigpOwogICAgfSBjYXRjaCAoZSkgewogICAgICBjb25zb2xlLmVycm9yKCJbcG9rZS1icm93c2VyIGV4dF0gc2V0dXBPZmZzY3JlZW4gZmFpbGVkOiIsIGUpOwogICAgfQogICAgdHJ5IHsKICAgICAgYnJpZGdlUG9ydD8ucG9zdE1lc3NhZ2UoeyB0eXBlOiAic3dfd2FrZSIgfSk7CiAgICB9IGNhdGNoIHsKICAgICAgLyogaWdub3JlICovCiAgICB9CiAgfSkoKTsKfSk7Cgphc3luYyBmdW5jdGlvbiBlbnN1cmVPZmZzY3JlZW5BbmRTY2hlZHVsZSgpIHsKICBpZiAoIShhd2FpdCBnZXRQb2tlRW5hYmxlZCgpKSkgewogICAgcmV0dXJuOwogIH0KICB0cnkgewogICAgYXdhaXQgc2V0dXBPZmZzY3JlZW4oKTsKICB9IGNhdGNoIChlKSB7CiAgICBjb25zb2xlLmVycm9yKCJbcG9rZS1icm93c2VyIGV4dF0gc2V0dXBPZmZzY3JlZW4gZmFpbGVkOiIsIGUpOwogIH0KICBzY2hlZHVsZUtlZXBBbGl2ZUFsYXJtKCk7Cn0KCmNocm9tZS5ydW50aW1lLm9uQ29ubmVjdC5hZGRMaXN0ZW5lcigocG9ydCkgPT4gewogIGlmIChwb3J0Lm5hbWUgIT09ICJQT0tFX1dTX0JSSURHRSIpIHJldHVybjsKICBicmlkZ2VQb3J0ID0gcG9ydDsKICBjb25zb2xlLmxvZygiW3Bva2UtYnJvd3NlciBleHRdIE9mZnNjcmVlbiBicmlkZ2UgcG9ydCBjb25uZWN0ZWQiKTsKICBwb3J0Lm9uTWVzc2FnZS5hZGRMaXN0ZW5lcigobXNnKSA9PiB7CiAgICBpZiAobXNnICYmIHR5cGVvZiBtc2cgPT09ICJvYmplY3QiICYmIG1zZy50eXBlID09PSAicmVxdWVzdF9oZWxsb19jcmVkZW50aWFscyIpIHsKICAgICAgdm9pZCBnZXRXc0F1dGhUb2tlbigpLnRoZW4oKHRva2VuKSA9PiB7CiAgICAgICAgdHJ5IHsKICAgICAgICAgIHBvcnQucG9zdE1lc3NhZ2UoewogICAgICAgICAgICB0eXBlOiAiaGVsbG9fY3JlZGVudGlhbHMiLAogICAgICAgICAgICB0b2tlbiwKICAgICAgICAgICAgdmVyc2lvbjogY2hyb21lLnJ1bnRpbWUuZ2V0TWFuaWZlc3QoKS52ZXJzaW9uLAogICAgICAgICAgfSk7CiAgICAgICAgfSBjYXRjaCB7CiAgICAgICAgICAvKiBpZ25vcmUgKi8KICAgICAgICB9CiAgICAgIH0pOwogICAgICByZXR1cm47CiAgICB9CiAgICBpZiAobXNnICYmIHR5cGVvZiBtc2cgPT09ICJvYmplY3QiICYmIG1zZy50eXBlID09PSAid3NfbWVzc2FnZSIgJiYgdHlwZW9mIG1zZy5kYXRhID09PSAic3RyaW5nIikgewogICAgICB2b2lkIGhhbmRsZVNvY2tldE1lc3NhZ2UobXNnLmRhdGEpLmNhdGNoKChlcnIpID0+IHsKICAgICAgICBsb2dDb21tYW5kKCJpbiIsIGBIYW5kbGVyIGVycm9yOiAke1N0cmluZyhlcnIpfWApOwogICAgICB9KTsKICAgICAgcmV0dXJuOwogICAgfQogICAgaWYgKG1zZyAmJiB0eXBlb2YgbXNnID09PSAib2JqZWN0IiAmJiBtc2cudHlwZSA9PT0gIndzX2ZyYW1lIiAmJiB0eXBlb2YgbXNnLnJhdyA9PT0gInN0cmluZyIpIHsKICAgICAgdm9pZCBoYW5kbGVTb2NrZXRNZXNzYWdlKG1zZy5yYXcpLmNhdGNoKChlcnIpID0+IHsKICAgICAgICBsb2dDb21tYW5kKCJpbiIsIGBIYW5kbGVyIGVycm9yOiAke1N0cmluZyhlcnIpfWApOwogICAgICB9KTsKICAgICAgcmV0dXJuOwogICAgfQogICAgaWYgKG1zZyAmJiB0eXBlb2YgbXNnID09PSAib2JqZWN0IiAmJiBtc2cudHlwZSA9PT0gIndzX2Nvbm5lY3RlZCIpIHsKICAgICAgc2V0U3RhdHVzKCJjb25uZWN0ZWQiKTsKICAgICAgcmV0dXJuOwogICAgfQogICAgaWYgKG1zZyAmJiB0eXBlb2YgbXNnID09PSAib2JqZWN0IiAmJiBtc2cudHlwZSA9PT0gIndzX2Rpc2Nvbm5lY3RlZCIpIHsKICAgICAgc2V0U3RhdHVzKCJkaXNjb25uZWN0ZWQiKTsKICAgICAgcmV0dXJuOwogICAgfQogICAgaWYgKG1zZyAmJiB0eXBlb2YgbXNnID09PSAib2JqZWN0IiAmJiBtc2cudHlwZSA9PT0gIndzX3N0YXR1cyIgJiYgdHlwZW9mIG1zZy5zdGF0dXMgPT09ICJzdHJpbmciKSB7CiAgICAgIHNldFN0YXR1cyhtc2cuc3RhdHVzKTsKICAgICAgcmV0dXJuOwogICAgfQogICAgaWYgKAogICAgICBtc2cgJiYKICAgICAgdHlwZW9mIG1zZyA9PT0gIm9iamVjdCIgJiYKICAgICAgbXNnLnR5cGUgPT09ICJ3c19sb2ciICYmCiAgICAgIHR5cGVvZiBtc2cuZGlyZWN0aW9uID09PSAic3RyaW5nIiAmJgogICAgICB0eXBlb2YgbXNnLnN1bW1hcnkgPT09ICJzdHJpbmciCiAgICApIHsKICAgICAgbG9nQ29tbWFuZChtc2cuZGlyZWN0aW9uLCBtc2cuc3VtbWFyeSk7CiAgICB9CiAgfSk7CiAgcG9ydC5vbkRpc2Nvbm5lY3QuYWRkTGlzdGVuZXIoKCkgPT4gewogICAgaWYgKGJyaWRnZVBvcnQgPT09IHBvcnQpIGJyaWRnZVBvcnQgPSBudWxsOwogICAgY29uc29sZS5sb2coIltwb2tlLWJyb3dzZXIgZXh0XSBPZmZzY3JlZW4gYnJpZGdlIHBvcnQgZGlzY29ubmVjdGVkIik7CiAgICBzZXRTdGF0dXMoImRpc2Nvbm5lY3RlZCIpOwogIH0pOwp9KTsKCi8qKgogKiBAcGFyYW0ge3Vua25vd259IGRhdGEKICovCmZ1bmN0aW9uIHNhZmVTZW5kKGRhdGEpIHsKICBpZiAoIWJyaWRnZVBvcnQpIHJldHVybiBmYWxzZTsKICB0cnkgewogICAgYnJpZGdlUG9ydC5wb3N0TWVzc2FnZSh7IHR5cGU6ICJ3c19zZW5kIiwgcGF5bG9hZDogZGF0YSB9KTsKICAgIHJldHVybiB0cnVlOwogIH0gY2F0Y2ggewogICAgcmV0dXJuIGZhbHNlOwogIH0KfQoKLyoqCiAqIEBwYXJhbSB7c3RyaW5nfSByYXcKICovCmFzeW5jIGZ1bmN0aW9uIGhhbmRsZVNvY2tldE1lc3NhZ2UocmF3KSB7CiAgLyoqIEB0eXBlIHtXc0luYm91bmR9ICovCiAgbGV0IG1zZzsKICB0cnkgewogICAgbXNnID0gSlNPTi5wYXJzZShyYXcpOwogIH0gY2F0Y2ggewogICAgbG9nQ29tbWFuZCgiaW4iLCAiSW52YWxpZCBKU09OIGZyb20gTUNQIik7CiAgICByZXR1cm47CiAgfQoKICBpZiAobXNnLnR5cGUgPT09ICJhdXRoX29rIikgewogICAgY29uc29sZS5sb2coIltwb2tlLWJyb3dzZXIgZXh0XSBBdXRoIE9LIHJlY2VpdmVkLCBjb25uZWN0aW9uIGZ1bGx5IGVzdGFibGlzaGVkIik7CiAgICBsb2dDb21tYW5kKCJvdXQiLCAiV2ViU29ja2V0OiBhdXRoIE9LIGZyb20gTUNQIik7CiAgICByZXR1cm47CiAgfQoKICBpZiAobXNnLnR5cGUgIT09ICJjb21tYW5kIiB8fCB0eXBlb2YgbXNnLnJlcXVlc3RJZCAhPT0gInN0cmluZyIgfHwgdHlwZW9mIG1zZy5jb21tYW5kICE9PSAic3RyaW5nIikgewogICAgcmV0dXJuOwogIH0KCiAgbG9nQ29tbWFuZCgiaW4iLCBgJHttc2cuY29tbWFuZH0gKCR7bXNnLnJlcXVlc3RJZC5zbGljZSgwLCA4KX3igKYpYCk7CiAgdHJ5IHsKICAgIGNvbnN0IHJlc3VsdCA9IGF3YWl0IGRpc3BhdGNoQ29tbWFuZChtc2cuY29tbWFuZCwgbXNnLnBheWxvYWQpOwogICAgc2FmZVNlbmQoeyB0eXBlOiAicmVzcG9uc2UiLCByZXF1ZXN0SWQ6IG1zZy5yZXF1ZXN0SWQsIG9rOiB0cnVlLCByZXN1bHQgfSk7CiAgICBsb2dDb21tYW5kKCJvdXQiLCBgT0sgJHttc2cuY29tbWFuZH1gKTsKICB9IGNhdGNoIChlcnIpIHsKICAgIGNvbnN0IGVycm9yID0gZXJyIGluc3RhbmNlb2YgRXJyb3IgPyBlcnIubWVzc2FnZSA6IFN0cmluZyhlcnIpOwogICAgc2FmZVNlbmQoeyB0eXBlOiAicmVzcG9uc2UiLCByZXF1ZXN0SWQ6IG1zZy5yZXF1ZXN0SWQsIG9rOiBmYWxzZSwgZXJyb3IgfSk7CiAgICBsb2dDb21tYW5kKCJvdXQiLCBgRVJSICR7bXNnLmNvbW1hbmR9OiAke2Vycm9yfWApOwogIH0KfQoKLyoqCiAqIEBwYXJhbSB7dW5rbm93bn0gcGF5bG9hZAogKi8KZnVuY3Rpb24gYXNQYXlsb2FkKHBheWxvYWQpIHsKICByZXR1cm4gLyoqIEB0eXBlIHtSZWNvcmQ8c3RyaW5nLCB1bmtub3duPn0gKi8gKHBheWxvYWQgJiYgdHlwZW9mIHBheWxvYWQgPT09ICJvYmplY3QiID8gcGF5bG9hZCA6IHt9KTsKfQoKLyoqCiAqIEBwYXJhbSB7bnVtYmVyIHwgdW5kZWZpbmVkfSB0YWJJZAogKi8KYXN5bmMgZnVuY3Rpb24gcmVzb2x2ZVRhYklkKHRhYklkKSB7CiAgaWYgKHR5cGVvZiB0YWJJZCA9PT0gIm51bWJlciIgJiYgTnVtYmVyLmlzRmluaXRlKHRhYklkKSkgewogICAgY29uc3QgdGFiID0gYXdhaXQgY2hyb21lLnRhYnMuZ2V0KHRhYklkKS5jYXRjaCgoKSA9PiBudWxsKTsKICAgIGlmICghdGFiKSB0aHJvdyBuZXcgRXJyb3IoYFRhYiBub3QgZm91bmQ6ICR7dGFiSWR9YCk7CiAgICByZXR1cm4gdGFiSWQ7CiAgfQogIGNvbnN0IFthY3RpdmVdID0gYXdhaXQgY2hyb21lLnRhYnMucXVlcnkoeyBhY3RpdmU6IHRydWUsIGxhc3RGb2N1c2VkV2luZG93OiB0cnVlIH0pOwogIGlmICghYWN0aXZlPy5pZCkgdGhyb3cgbmV3IEVycm9yKCJObyBhY3RpdmUgdGFiIik7CiAgcmV0dXJuIGFjdGl2ZS5pZDsKfQoKLyoqCiAqIE1lcmdlIENocm9tZSB0YWIgbWV0YWRhdGEgaW50byB0b29sIHJlc3VsdHMgKHRhYklkLCB1cmwsIHRpdGxlIGZyb20gdGFicy5nZXQpLgogKiBAcGFyYW0ge251bWJlcn0gdGFiSWQKICogQHBhcmFtIHt1bmtub3dufSB2YWx1ZQogKi8KYXN5bmMgZnVuY3Rpb24gd2l0aFRhYk1ldGEodGFiSWQsIHZhbHVlKSB7CiAgY29uc3QgdGFiID0gYXdhaXQgY2hyb21lLnRhYnMuZ2V0KHRhYklkKS5jYXRjaCgoKSA9PiBudWxsKTsKICBjb25zdCBtZXRhID0gewogICAgdGFiSWQsCiAgICB1cmw6IHRhYj8udXJsID8/ICIiLAogICAgdGl0bGU6IHRhYj8udGl0bGUgPz8gIiIsCiAgfTsKICBpZiAodmFsdWUgJiYgdHlwZW9mIHZhbHVlID09PSAib2JqZWN0IiAmJiAhQXJyYXkuaXNBcnJheSh2YWx1ZSkpIHsKICAgIHJldHVybiB7IC4uLigvKiogQHR5cGUge1JlY29yZDxzdHJpbmcsIHVua25vd24+fSAqLyAodmFsdWUpKSwgLi4ubWV0YSB9OwogIH0KICByZXR1cm4geyAuLi5tZXRhLCB2YWx1ZSB9Owp9CgovKioKICogQnJpbmcgYSB0YWIgdG8gdGhlIGZvcmVncm91bmQgc28gY2FwdHVyZVZpc2libGVUYWIgdGFyZ2V0cyBpdC4KICogQHBhcmFtIHtudW1iZXJ9IHRhYklkCiAqLwphc3luYyBmdW5jdGlvbiBlbnN1cmVUYWJWaXNpYmxlRm9yQ2FwdHVyZSh0YWJJZCkgewogIGNvbnN0IHRhYiA9IGF3YWl0IGNocm9tZS50YWJzLmdldCh0YWJJZCkuY2F0Y2goKCkgPT4gbnVsbCk7CiAgaWYgKCF0YWI/LmlkIHx8IHRhYi53aW5kb3dJZCA9PSBudWxsKSB0aHJvdyBuZXcgRXJyb3IoYFRhYiBub3QgZm91bmQ6ICR7dGFiSWR9YCk7CiAgaWYgKCF0YWIuYWN0aXZlKSB7CiAgICBhd2FpdCBjaHJvbWUudGFicy51cGRhdGUodGFiSWQsIHsgYWN0aXZlOiB0cnVlIH0pOwogIH0KICBhd2FpdCBjaHJvbWUud2luZG93cy51cGRhdGUodGFiLndpbmRvd0lkLCB7IGZvY3VzZWQ6IHRydWUgfSk7CiAgYXdhaXQgbmV3IFByb21pc2UoKHIpID0+IHNldFRpbWVvdXQociwgNzUpKTsKICByZXR1cm4gdGFiOwp9CgovKioKICogQHBhcmFtIHtudW1iZXJ9IHRhYklkCiAqIEBwYXJhbSB7c3RyaW5nfSBtZXRob2QKICogQHBhcmFtIHtvYmplY3R9IFtwYXJhbXNdCiAqLwpmdW5jdGlvbiBkZWJ1Z2dlclNlbmQodGFiSWQsIG1ldGhvZCwgcGFyYW1zID0ge30pIHsKICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4gewogICAgY2hyb21lLmRlYnVnZ2VyLnNlbmRDb21tYW5kKHsgdGFiSWQgfSwgbWV0aG9kLCBwYXJhbXMsICgpID0+IHsKICAgICAgY29uc3QgZXJyID0gY2hyb21lLnJ1bnRpbWUubGFzdEVycm9yOwogICAgICBpZiAoZXJyKSByZWplY3QobmV3IEVycm9yKGVyci5tZXNzYWdlKSk7CiAgICAgIGVsc2UgcmVzb2x2ZSh1bmRlZmluZWQpOwogICAgfSk7CiAgfSk7Cn0KCi8qKgogKiBAcGFyYW0ge251bWJlcn0gdGFiSWQKICogQHBhcmFtIHtzdHJpbmd9IG1ldGhvZAogKiBAcGFyYW0ge29iamVjdH0gW3BhcmFtc10KICogQHJldHVybnMge1Byb21pc2U8dW5rbm93bj59CiAqLwpmdW5jdGlvbiBkZWJ1Z2dlclNlbmRXaXRoUmVzdWx0KHRhYklkLCBtZXRob2QsIHBhcmFtcyA9IHt9KSB7CiAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHsKICAgIGNocm9tZS5kZWJ1Z2dlci5zZW5kQ29tbWFuZCh7IHRhYklkIH0sIG1ldGhvZCwgcGFyYW1zLCAocmVzdWx0KSA9PiB7CiAgICAgIGNvbnN0IGVyciA9IGNocm9tZS5ydW50aW1lLmxhc3RFcnJvcjsKICAgICAgaWYgKGVycikgcmVqZWN0KG5ldyBFcnJvcihlcnIubWVzc2FnZSkpOwogICAgICBlbHNlIHJlc29sdmUocmVzdWx0KTsKICAgIH0pOwogIH0pOwp9CgovKioKICogQHBhcmFtIHtudW1iZXJ9IHRhYklkCiAqLwphc3luYyBmdW5jdGlvbiBkZWJ1Z2dlckF0dGFjaCh0YWJJZCkgewogIGF3YWl0IG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHsKICAgIGNocm9tZS5kZWJ1Z2dlci5hdHRhY2goeyB0YWJJZCB9LCAiMS4zIiwgKCkgPT4gewogICAgICBjb25zdCBlcnIgPSBjaHJvbWUucnVudGltZS5sYXN0RXJyb3I7CiAgICAgIGlmIChlcnIpIHJlamVjdChuZXcgRXJyb3IoZXJyLm1lc3NhZ2UpKTsKICAgICAgZWxzZSByZXNvbHZlKHVuZGVmaW5lZCk7CiAgICB9KTsKICB9KTsKfQoKLyoqCiAqIEBwYXJhbSB7bnVtYmVyfSB0YWJJZAogKi8KYXN5bmMgZnVuY3Rpb24gZGVidWdnZXJEZXRhY2godGFiSWQpIHsKICBhd2FpdCBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4gewogICAgY2hyb21lLmRlYnVnZ2VyLmRldGFjaCh7IHRhYklkIH0sICgpID0+IHJlc29sdmUoKSk7CiAgfSk7Cn0KCi8qKgogKiBAcGFyYW0ge251bWJlcn0gdGFiSWQKICovCmZ1bmN0aW9uIGlzTmV0d29ya0NhcHR1cmluZyh0YWJJZCkgewogIHJldHVybiBuZXR3b3JrQ2FwdHVyZVRhYnMuaGFzKHRhYklkKTsKfQoKLyoqCiAqIEBwYXJhbSB7bnVtYmVyfSB0YWJJZAogKi8KYXN5bmMgZnVuY3Rpb24gZGVidWdnZXJBdHRhY2hGb3JUb29sKHRhYklkKSB7CiAgaWYgKGlzTmV0d29ya0NhcHR1cmluZyh0YWJJZCkpIHJldHVybjsKICBhd2FpdCBkZWJ1Z2dlckF0dGFjaCh0YWJJZCk7Cn0KCi8qKgogKiBAcGFyYW0ge251bWJlcn0gdGFiSWQKICovCmFzeW5jIGZ1bmN0aW9uIGRlYnVnZ2VyRGV0YWNoRm9yVG9vbCh0YWJJZCkgewogIGlmIChpc05ldHdvcmtDYXB0dXJpbmcodGFiSWQpKSByZXR1cm47CiAgYXdhaXQgZGVidWdnZXJEZXRhY2godGFiSWQpOwp9CgovKioKICogQHBhcmFtIHt1bmtub3dufSBoZWFkZXJzCiAqIEByZXR1cm5zIHtSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+fQogKi8KZnVuY3Rpb24gbm9ybWFsaXplSGVhZGVycyhoZWFkZXJzKSB7CiAgaWYgKCFoZWFkZXJzIHx8IHR5cGVvZiBoZWFkZXJzICE9PSAib2JqZWN0IikgcmV0dXJuIHt9OwogIGlmIChBcnJheS5pc0FycmF5KGhlYWRlcnMpKSB7CiAgICAvKiogQHR5cGUge1JlY29yZDxzdHJpbmcsIHN0cmluZz59ICovCiAgICBjb25zdCBvID0ge307CiAgICBmb3IgKGNvbnN0IHJvdyBvZiBoZWFkZXJzKSB7CiAgICAgIGlmIChyb3cgJiYgdHlwZW9mIHJvdyA9PT0gIm9iamVjdCIgJiYgIm5hbWUiIGluIHJvdykgewogICAgICAgIGNvbnN0IG5hbWUgPSBTdHJpbmcoLyoqIEB0eXBlIHt7IG5hbWU/OiBzdHJpbmcgfX0gKi8gKHJvdykubmFtZSA/PyAiIik7CiAgICAgICAgaWYgKG5hbWUpIG9bbmFtZV0gPSBTdHJpbmcoLyoqIEB0eXBlIHt7IHZhbHVlPzogc3RyaW5nIH19ICovIChyb3cpLnZhbHVlID8/ICIiKTsKICAgICAgfQogICAgfQogICAgcmV0dXJuIG87CiAgfQogIC8qKiBAdHlwZSB7UmVjb3JkPHN0cmluZywgc3RyaW5nPn0gKi8KICBjb25zdCBvdXQgPSB7fTsKICBmb3IgKGNvbnN0IFtrLCB2XSBvZiBPYmplY3QuZW50cmllcygvKiogQHR5cGUge1JlY29yZDxzdHJpbmcsIHVua25vd24+fSAqLyAoaGVhZGVycykpKSB7CiAgICBvdXRba10gPSB0eXBlb2YgdiA9PT0gInN0cmluZyIgPyB2IDogSlNPTi5zdHJpbmdpZnkodik7CiAgfQogIHJldHVybiBvdXQ7Cn0KCi8qKgogKiBAcGFyYW0ge251bWJlcn0gdGFiSWQKICogQHBhcmFtIHtzdHJpbmd9IHJlcXVlc3RJZAogKiBAcGFyYW0ge1JlY29yZDxzdHJpbmcsIHVua25vd24+fSBwYXRjaAogKi8KZnVuY3Rpb24gdXBzZXJ0TmV0d29ya0VudHJ5KHRhYklkLCByZXF1ZXN0SWQsIHBhdGNoKSB7CiAgbGV0IHN0YXRlID0gbmV0d29ya1N0YXRlQnlUYWIuZ2V0KHRhYklkKTsKICBpZiAoIXN0YXRlKSB7CiAgICBzdGF0ZSA9IHsgb3JkZXI6IFtdLCBieUlkOiBuZXcgTWFwKCkgfTsKICAgIG5ldHdvcmtTdGF0ZUJ5VGFiLnNldCh0YWJJZCwgc3RhdGUpOwogIH0KICBjb25zdCBleGlzdGluZyA9IHN0YXRlLmJ5SWQuZ2V0KHJlcXVlc3RJZCk7CiAgaWYgKGV4aXN0aW5nKSB7CiAgICBPYmplY3QuYXNzaWduKGV4aXN0aW5nLCBwYXRjaCk7CiAgfSBlbHNlIHsKICAgIHN0YXRlLmJ5SWQuc2V0KHJlcXVlc3RJZCwgeyByZXF1ZXN0SWQsIC4uLnBhdGNoIH0pOwogICAgc3RhdGUub3JkZXIucHVzaChyZXF1ZXN0SWQpOwogICAgd2hpbGUgKHN0YXRlLm9yZGVyLmxlbmd0aCA+IE1BWF9ORVRfUEVSX1RBQikgewogICAgICBjb25zdCBkcm9wID0gc3RhdGUub3JkZXIuc2hpZnQoKTsKICAgICAgaWYgKGRyb3ApIHN0YXRlLmJ5SWQuZGVsZXRlKGRyb3ApOwogICAgfQogIH0KfQoKY2hyb21lLmRlYnVnZ2VyLm9uRXZlbnQuYWRkTGlzdGVuZXIoKHNvdXJjZSwgbWV0aG9kLCBwYXJhbXMpID0+IHsKICBjb25zdCB0YWJJZCA9IHNvdXJjZS50YWJJZDsKICBpZiAodGFiSWQgPT0gbnVsbCB8fCAhbmV0d29ya0NhcHR1cmVUYWJzLmhhcyh0YWJJZCkpIHJldHVybjsKICBjb25zdCBwID0gcGFyYW1zICYmIHR5cGVvZiBwYXJhbXMgPT09ICJvYmplY3QiID8gLyoqIEB0eXBlIHtSZWNvcmQ8c3RyaW5nLCB1bmtub3duPn0gKi8gKHBhcmFtcykgOiB7fTsKICBjb25zdCByaWQgPSBwLnJlcXVlc3RJZCAhPSBudWxsID8gU3RyaW5nKHAucmVxdWVzdElkKSA6ICIiOwogIGlmICghcmlkKSByZXR1cm47CgogIGlmIChtZXRob2QgPT09ICJOZXR3b3JrLnJlcXVlc3RXaWxsQmVTZW50IikgewogICAgY29uc3QgcmVxID0gLyoqIEB0eXBlIHt7IHVybD86IHN0cmluZzsgbWV0aG9kPzogc3RyaW5nOyBoZWFkZXJzPzogdW5rbm93biB9fSAqLyAocC5yZXF1ZXN0ID8/IHt9KTsKICAgIHVwc2VydE5ldHdvcmtFbnRyeSh0YWJJZCwgcmlkLCB7CiAgICAgIHVybDogcmVxLnVybCA/PyAiIiwKICAgICAgbWV0aG9kOiByZXEubWV0aG9kID8/ICJHRVQiLAogICAgICByZXF1ZXN0SGVhZGVyczogbm9ybWFsaXplSGVhZGVycyhyZXEuaGVhZGVycyksCiAgICB9KTsKICB9IGVsc2UgaWYgKG1ldGhvZCA9PT0gIk5ldHdvcmsucmVzcG9uc2VSZWNlaXZlZCIpIHsKICAgIGNvbnN0IHJlcyA9IC8qKiBAdHlwZSB7eyBzdGF0dXM/OiBudW1iZXI7IG1pbWVUeXBlPzogc3RyaW5nOyBoZWFkZXJzPzogdW5rbm93bjsgdGltaW5nPzogdW5rbm93biB9fSAqLyAocC5yZXNwb25zZSA/PyB7fSk7CiAgICB1cHNlcnROZXR3b3JrRW50cnkodGFiSWQsIHJpZCwgewogICAgICBzdGF0dXM6IHJlcy5zdGF0dXMsCiAgICAgIG1pbWVUeXBlOiByZXMubWltZVR5cGUsCiAgICAgIHJlc3BvbnNlSGVhZGVyczogbm9ybWFsaXplSGVhZGVycyhyZXMuaGVhZGVycyksCiAgICAgIHRpbWluZzogcmVzLnRpbWluZyA/PyBudWxsLAogICAgfSk7CiAgfSBlbHNlIGlmIChtZXRob2QgPT09ICJOZXR3b3JrLmxvYWRpbmdGaW5pc2hlZCIpIHsKICAgIHVwc2VydE5ldHdvcmtFbnRyeSh0YWJJZCwgcmlkLCB7CiAgICAgIGJvZHlTaXplOiB0eXBlb2YgcC5lbmNvZGVkRGF0YUxlbmd0aCA9PT0gIm51bWJlciIgPyBwLmVuY29kZWREYXRhTGVuZ3RoIDogdW5kZWZpbmVkLAogICAgICBsb2FkZWQ6IHRydWUsCiAgICB9KTsKICB9Cn0pOwoKY2hyb21lLmRlYnVnZ2VyLm9uRGV0YWNoLmFkZExpc3RlbmVyKChzb3VyY2UsIF9yZWFzb24pID0+IHsKICBpZiAoc291cmNlLnRhYklkICE9IG51bGwpIG5ldHdvcmtDYXB0dXJlVGFicy5kZWxldGUoc291cmNlLnRhYklkKTsKfSk7CgovKioKICogQHBhcmFtIHtudW1iZXJ9IHRhYklkCiAqIEBwYXJhbSB7bnVtYmVyfSB4CiAqIEBwYXJhbSB7bnVtYmVyfSB5CiAqLwphc3luYyBmdW5jdGlvbiBjbGlja1ZpYURlYnVnZ2VyKHRhYklkLCB4LCB5KSB7CiAgYXdhaXQgZGVidWdnZXJBdHRhY2hGb3JUb29sKHRhYklkKTsKICB0cnkgewogICAgYXdhaXQgZGVidWdnZXJTZW5kKHRhYklkLCAiSW5wdXQuZGlzcGF0Y2hNb3VzZUV2ZW50IiwgewogICAgICB0eXBlOiAibW91c2VQcmVzc2VkIiwKICAgICAgeCwKICAgICAgeSwKICAgICAgYnV0dG9uOiAibGVmdCIsCiAgICAgIGNsaWNrQ291bnQ6IDEsCiAgICB9KTsKICAgIGF3YWl0IGRlYnVnZ2VyU2VuZCh0YWJJZCwgIklucHV0LmRpc3BhdGNoTW91c2VFdmVudCIsIHsKICAgICAgdHlwZTogIm1vdXNlUmVsZWFzZWQiLAogICAgICB4LAogICAgICB5LAogICAgICBidXR0b246ICJsZWZ0IiwKICAgICAgY2xpY2tDb3VudDogMSwKICAgIH0pOwogICAgcmV0dXJuIHsgc3VjY2VzczogdHJ1ZSB9OwogIH0gZmluYWxseSB7CiAgICBhd2FpdCBkZWJ1Z2dlckRldGFjaEZvclRvb2wodGFiSWQpOwogIH0KfQoKLyoqCiAqIFNlbGVjdC1hbGwgdGhlbiBCYWNrc3BhY2UgdmlhIENEUCBzbyB0aGUgZm9jdXNlZCBmaWVsZCBpcyBjbGVhcmVkIGJlZm9yZSB0eXBpbmcuCiAqIEBwYXJhbSB7bnVtYmVyfSB0YWJJZAogKi8KYXN5bmMgZnVuY3Rpb24gY2xlYXJGb2N1c2VkRmllbGRWaWFEZWJ1Z2dlcktleXModGFiSWQpIHsKICBjb25zdCBpbmZvID0gYXdhaXQgY2hyb21lLnJ1bnRpbWUuZ2V0UGxhdGZvcm1JbmZvKCk7CiAgY29uc3QgbW9kID0gaW5mby5vcyA9PT0gIm1hYyIgPyA0IDogMjsKICBhd2FpdCBkZWJ1Z2dlclNlbmQodGFiSWQsICJJbnB1dC5kaXNwYXRjaEtleUV2ZW50IiwgewogICAgdHlwZTogImtleURvd24iLAogICAga2V5OiAiYSIsCiAgICBjb2RlOiAiS2V5QSIsCiAgICB3aW5kb3dzVmlydHVhbEtleUNvZGU6IDY1LAogICAgbW9kaWZpZXJzOiBtb2QsCiAgfSk7CiAgYXdhaXQgZGVidWdnZXJTZW5kKHRhYklkLCAiSW5wdXQuZGlzcGF0Y2hLZXlFdmVudCIsIHsKICAgIHR5cGU6ICJrZXlVcCIsCiAgICBrZXk6ICJhIiwKICAgIGNvZGU6ICJLZXlBIiwKICAgIHdpbmRvd3NWaXJ0dWFsS2V5Q29kZTogNjUsCiAgICBtb2RpZmllcnM6IG1vZCwKICB9KTsKICBhd2FpdCBkZWJ1Z2dlclNlbmQodGFiSWQsICJJbnB1dC5kaXNwYXRjaEtleUV2ZW50IiwgewogICAgdHlwZTogImtleURvd24iLAogICAga2V5OiAiQmFja3NwYWNlIiwKICAgIGNvZGU6ICJCYWNrc3BhY2UiLAogICAgd2luZG93c1ZpcnR1YWxLZXlDb2RlOiA4LAogIH0pOwogIGF3YWl0IGRlYnVnZ2VyU2VuZCh0YWJJZCwgIklucHV0LmRpc3BhdGNoS2V5RXZlbnQiLCB7CiAgICB0eXBlOiAia2V5VXAiLAogICAga2V5OiAiQmFja3NwYWNlIiwKICAgIGNvZGU6ICJCYWNrc3BhY2UiLAogICAgd2luZG93c1ZpcnR1YWxLZXlDb2RlOiA4LAogIH0pOwp9CgovKioKICogQHBhcmFtIHtudW1iZXJ9IHRhYklkCiAqIEBwYXJhbSB7c3RyaW5nfSB0ZXh0CiAqIEBwYXJhbSB7Ym9vbGVhbn0gW2NsZWFyRmllbGRdCiAqLwphc3luYyBmdW5jdGlvbiB0eXBlVGV4dFZpYURlYnVnZ2VyKHRhYklkLCB0ZXh0LCBjbGVhckZpZWxkKSB7CiAgYXdhaXQgZGVidWdnZXJBdHRhY2hGb3JUb29sKHRhYklkKTsKICB0cnkgewogICAgaWYgKGNsZWFyRmllbGQpIHsKICAgICAgYXdhaXQgY2xlYXJGb2N1c2VkRmllbGRWaWFEZWJ1Z2dlcktleXModGFiSWQpOwogICAgfQogICAgLy8gVXNlIElucHV0Lmluc2VydFRleHQgZm9yIGVhY2ggdGV4dCBzZWdtZW50IOKAlCB0aGUgY29ycmVjdCBDRFAgY29tbWFuZCBmb3IKICAgIC8vIGluc2VydGluZyBpbnRvIGZvY3VzZWQgZmllbGRzIChmaXJlcyBiZWZvcmVpbnB1dCArIGlucHV0LCB0cmlnZ2VyaW5nIFJlYWN0L0RyYWZ0LmpzKS4KICAgIC8vIEhhbmRsZSBuZXdsaW5lcyB3aXRoIHByb3BlciBFbnRlciBrZXkgZXZlbnRzIHNpbmNlIGluc2VydFRleHQgd29uJ3QgZmlyZSB0aGVtLgogICAgY29uc3Qgc2VnbWVudHMgPSB0ZXh0LnNwbGl0KC8oXG4pLyk7CiAgICBmb3IgKGNvbnN0IHNlZ21lbnQgb2Ygc2VnbWVudHMpIHsKICAgICAgaWYgKHNlZ21lbnQgPT09ICJcbiIpIHsKICAgICAgICBhd2FpdCBkZWJ1Z2dlclNlbmQodGFiSWQsICJJbnB1dC5kaXNwYXRjaEtleUV2ZW50IiwgewogICAgICAgICAgdHlwZTogImtleURvd24iLAogICAgICAgICAga2V5OiAiRW50ZXIiLAogICAgICAgICAgY29kZTogIkVudGVyIiwKICAgICAgICAgIHdpbmRvd3NWaXJ0dWFsS2V5Q29kZTogMTMsCiAgICAgICAgICBuYXRpdmVWaXJ0dWFsS2V5Q29kZTogMTMsCiAgICAgICAgICB1bm1vZGlmaWVkVGV4dDogIlxyIiwKICAgICAgICAgIHRleHQ6ICJcciIsCiAgICAgICAgfSk7CiAgICAgICAgYXdhaXQgZGVidWdnZXJTZW5kKHRhYklkLCAiSW5wdXQuZGlzcGF0Y2hLZXlFdmVudCIsIHsKICAgICAgICAgIHR5cGU6ICJrZXlVcCIsCiAgICAgICAgICBrZXk6ICJFbnRlciIsCiAgICAgICAgICBjb2RlOiAiRW50ZXIiLAogICAgICAgICAgd2luZG93c1ZpcnR1YWxLZXlDb2RlOiAxMywKICAgICAgICAgIG5hdGl2ZVZpcnR1YWxLZXlDb2RlOiAxMywKICAgICAgICB9KTsKICAgICAgfSBlbHNlIGlmIChzZWdtZW50Lmxlbmd0aCA+IDApIHsKICAgICAgICBhd2FpdCBkZWJ1Z2dlclNlbmQodGFiSWQsICJJbnB1dC5pbnNlcnRUZXh0IiwgeyB0ZXh0OiBzZWdtZW50IH0pOwogICAgICB9CiAgICB9CiAgICByZXR1cm4geyBzdWNjZXNzOiB0cnVlLCBjaGFyc1R5cGVkOiB0ZXh0Lmxlbmd0aCB9OwogIH0gZmluYWxseSB7CiAgICBhd2FpdCBkZWJ1Z2dlckRldGFjaEZvclRvb2wodGFiSWQpOwogIH0KfQoKLyoqCiAqIFRlbXBvcmFyeSB2aWV3cG9ydCBkb3QgZm9yIGNvb3JkaW5hdGUtYmFzZWQgdG9vbHMuIFNlcmlhbGl6ZWQgaW50byB0aGUgcGFnZSBieSBjaHJvbWUuc2NyaXB0aW5nLgogKiBAcGFyYW0ge3sgeD86IHVua25vd247IHk/OiB1bmtub3duIH19IHAKICovCmZ1bmN0aW9uIHBva2VJbmplY3RlZEN1cnNvckZlZWRiYWNrRG90KHApIHsKICBjb25zdCB4ID0gdHlwZW9mIHAueCA9PT0gIm51bWJlciIgPyBwLnggOiBOdW1iZXIocC54KTsKICBjb25zdCB5ID0gdHlwZW9mIHAueSA9PT0gIm51bWJlciIgPyBwLnkgOiBOdW1iZXIocC55KTsKICBpZiAoIU51bWJlci5pc0Zpbml0ZSh4KSB8fCAhTnVtYmVyLmlzRmluaXRlKHkpKSByZXR1cm47CgogIGNvbnN0IGRvdCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImRpdiIpOwogIGRvdC5zZXRBdHRyaWJ1dGUoImRhdGEtcG9rZS1jdXJzb3ItZmVlZGJhY2siLCAiMSIpOwogIE9iamVjdC5hc3NpZ24oZG90LnN0eWxlLCB7CiAgICBwb3NpdGlvbjogImZpeGVkIiwKICAgIGxlZnQ6IGAke3ggLSA4fXB4YCwKICAgIHRvcDogYCR7eSAtIDh9cHhgLAogICAgd2lkdGg6ICIxNnB4IiwKICAgIGhlaWdodDogIjE2cHgiLAogICAgYm9yZGVyUmFkaXVzOiAiNTAlIiwKICAgIGJhY2tncm91bmQ6ICJyZ2IoMjU1LCAwLCAwKSIsCiAgICB6SW5kZXg6ICI5OTk5OTkiLAogICAgcG9pbnRlckV2ZW50czogIm5vbmUiLAogICAgb3BhY2l0eTogIjEiLAogICAgdHJhbnNpdGlvbjogIm9wYWNpdHkgNjAwbXMgZWFzZS1vdXQiLAogICAgYm94U2l6aW5nOiAiYm9yZGVyLWJveCIsCiAgfSk7CiAgKGRvY3VtZW50LmRvY3VtZW50RWxlbWVudCB8fCBkb2N1bWVudC5ib2R5KS5hcHBlbmRDaGlsZChkb3QpOwogIHJlcXVlc3RBbmltYXRpb25GcmFtZSgoKSA9PiB7CiAgICByZXF1ZXN0QW5pbWF0aW9uRnJhbWUoKCkgPT4gewogICAgICBkb3Quc3R5bGUub3BhY2l0eSA9ICIwIjsKICAgIH0pOwogIH0pOwogIHNldFRpbWVvdXQoKCkgPT4gZG90LnJlbW92ZSgpLCA2NTApOwp9CgovKioKICogQHBhcmFtIHtudW1iZXJ9IHRhYklkCiAqIEBwYXJhbSB7bnVtYmVyfSB4CiAqIEBwYXJhbSB7bnVtYmVyfSB5CiAqLwphc3luYyBmdW5jdGlvbiBzaG93Q3Vyc29yRmVlZGJhY2tEb3QodGFiSWQsIHgsIHkpIHsKICB0cnkgewogICAgYXdhaXQgY2hyb21lLnNjcmlwdGluZy5leGVjdXRlU2NyaXB0KHsKICAgICAgdGFyZ2V0OiB7IHRhYklkLCBhbGxGcmFtZXM6IGZhbHNlIH0sCiAgICAgIHdvcmxkOiAiTUFJTiIsCiAgICAgIGluamVjdEltbWVkaWF0ZWx5OiB0cnVlLAogICAgICBmdW5jOiBwb2tlSW5qZWN0ZWRDdXJzb3JGZWVkYmFja0RvdCwKICAgICAgYXJnczogW3sgeCwgeSB9XSwKICAgIH0pOwogIH0gY2F0Y2ggewogICAgLyogY2hyb21lOi8vIGFuZCBvdGhlciByZXN0cmljdGVkIHRhYnMg4oCUIGF1dG9tYXRpb24gY29udGludWVzICovCiAgfQp9CgovKioKICogQHBhcmFtIHtudW1iZXJ9IHRhYklkCiAqIEBwYXJhbSB7bnVtYmVyfSB0aW1lb3V0TXMKICovCmZ1bmN0aW9uIHdhaXRGb3JUYWJMb2FkQ29tcGxldGUodGFiSWQsIHRpbWVvdXRNcykgewogIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7CiAgICBjb25zdCB0aW1lciA9IHNldFRpbWVvdXQoKCkgPT4gewogICAgICBjaHJvbWUudGFicy5vblVwZGF0ZWQucmVtb3ZlTGlzdGVuZXIob25VcGRhdGVkKTsKICAgICAgcmVqZWN0KG5ldyBFcnJvcigibmF2aWdhdGVfdG86IGxvYWQgdGltZW91dCIpKTsKICAgIH0sIHRpbWVvdXRNcyk7CgogICAgLyoqCiAgICAgKiBAcGFyYW0ge251bWJlcn0gaWQKICAgICAqIEBwYXJhbSB7Y2hyb21lLnRhYnMuVGFiQ2hhbmdlSW5mb30gY2hhbmdlSW5mbwogICAgICovCiAgICBmdW5jdGlvbiBvblVwZGF0ZWQoaWQsIGNoYW5nZUluZm8pIHsKICAgICAgaWYgKGlkICE9PSB0YWJJZCkgcmV0dXJuOwogICAgICBpZiAoY2hhbmdlSW5mby5zdGF0dXMgPT09ICJjb21wbGV0ZSIpIHsKICAgICAgICBjbGVhclRpbWVvdXQodGltZXIpOwogICAgICAgIGNocm9tZS50YWJzLm9uVXBkYXRlZC5yZW1vdmVMaXN0ZW5lcihvblVwZGF0ZWQpOwogICAgICAgIHJlc29sdmUoKTsKICAgICAgfQogICAgfQoKICAgIGNocm9tZS50YWJzLm9uVXBkYXRlZC5hZGRMaXN0ZW5lcihvblVwZGF0ZWQpOwogIH0pOwp9Cgphc3luYyBmdW5jdGlvbiBoYW5kbGVMaXN0VGFicygpIHsKICBjb25zdCB0YWJzID0gYXdhaXQgY2hyb21lLnRhYnMucXVlcnkoe30pOwogIHJldHVybiB0YWJzCiAgICAuZmlsdGVyKCh0KSA9PiB0LmlkICE9IG51bGwpCiAgICAubWFwKCh0KSA9PiAoewogICAgICB0YWJJZDogdC5pZCwKICAgICAgdGl0bGU6IHQudGl0bGUgPz8gIiIsCiAgICAgIHVybDogdC51cmwgPz8gIiIsCiAgICAgIGFjdGl2ZTogQm9vbGVhbih0LmFjdGl2ZSksCiAgICAgIGluZGV4OiB0LmluZGV4LAogICAgfSkpOwp9Cgphc3luYyBmdW5jdGlvbiBoYW5kbGVHZXRBY3RpdmVUYWIoKSB7CiAgY29uc3QgW3RhYl0gPSBhd2FpdCBjaHJvbWUudGFicy5xdWVyeSh7IGFjdGl2ZTogdHJ1ZSwgbGFzdEZvY3VzZWRXaW5kb3c6IHRydWUgfSk7CiAgaWYgKCF0YWI/LmlkKSB0aHJvdyBuZXcgRXJyb3IoIk5vIGFjdGl2ZSB0YWIiKTsKICByZXR1cm4gewogICAgdGFiSWQ6IHRhYi5pZCwKICAgIHRpdGxlOiB0YWIudGl0bGUgPz8gIiIsCiAgICB1cmw6IHRhYi51cmwgPz8gIiIsCiAgICBhY3RpdmU6IHRydWUsCiAgICBpbmRleDogdGFiLmluZGV4LAogIH07Cn0KCi8qKiBAcGFyYW0ge3Vua25vd259IHBheWxvYWQgKi8KYXN5bmMgZnVuY3Rpb24gaGFuZGxlTmF2aWdhdGVUbyhwYXlsb2FkKSB7CiAgY29uc3QgcCA9IGFzUGF5bG9hZChwYXlsb2FkKTsKICBjb25zdCB1cmwgPSB0eXBlb2YgcC51cmwgPT09ICJzdHJpbmciID8gcC51cmwgOiAiIjsKICBpZiAoIXVybCkgdGhyb3cgbmV3IEVycm9yKCJuYXZpZ2F0ZV90byByZXF1aXJlcyB1cmwiKTsKICBjb25zdCB0YWJJZCA9IGF3YWl0IHJlc29sdmVUYWJJZCh0eXBlb2YgcC50YWJJZCA9PT0gIm51bWJlciIgPyBwLnRhYklkIDogdW5kZWZpbmVkKTsKICAvKiogQWx3YXlzIHdhaXQgZm9yIGNocm9tZS50YWJzLm9uVXBkYXRlZCBzdGF0dXMgImNvbXBsZXRlIiBzbyBmaW5hbFVybC90aXRsZSBtYXRjaCB0aGUgbG9hZGVkIHBhZ2UgKG5vdCBhIHN0YWxlIGRldnRvb2xzL2ludGVyc3RpdGlhbCBVUkwpLiAqLwogIGNvbnN0IHRpbWVvdXRNcyA9IHAud2FpdEZvckxvYWQgPT09IGZhbHNlID8gMTBfMDAwIDogTkFWSUdBVEVfV0FJVF9NUzsKICBjb25zdCBkb25lID0gd2FpdEZvclRhYkxvYWRDb21wbGV0ZSh0YWJJZCwgdGltZW91dE1zKTsKICBhd2FpdCBjaHJvbWUudGFicy51cGRhdGUodGFiSWQsIHsgdXJsIH0pOwogIGF3YWl0IGRvbmU7CiAgY29uc3QgdGFiID0gYXdhaXQgY2hyb21lLnRhYnMuZ2V0KHRhYklkKTsKICBjb25zdCBmaW5hbFVybCA9IHRhYi51cmwgPz8gIiI7CiAgY29uc3QgdGl0bGUgPSB0YWIudGl0bGUgPz8gIiI7CiAgcmV0dXJuIHsKICAgIHN1Y2Nlc3M6IHRydWUsCiAgICB0YWJJZCwKICAgIHVybDogZmluYWxVcmwsCiAgICBmaW5hbFVybCwKICAgIHRpdGxlLAogIH07Cn0KCi8qKiBAcGFyYW0ge3Vua25vd259IHBheWxvYWQgKi8KYXN5bmMgZnVuY3Rpb24gaGFuZGxlQ2xpY2tFbGVtZW50KHBheWxvYWQpIHsKICBjb25zdCBwID0gYXNQYXlsb2FkKHBheWxvYWQpOwogIGNvbnN0IHRhYklkID0gYXdhaXQgcmVzb2x2ZVRhYklkKHR5cGVvZiBwLnRhYklkID09PSAibnVtYmVyIiA/IHAudGFiSWQgOiB1bmRlZmluZWQpOwogIGNvbnN0IHNlbGVjdG9yID0gdHlwZW9mIHAuc2VsZWN0b3IgPT09ICJzdHJpbmciID8gcC5zZWxlY3Rvci50cmltKCkgOiAiIjsKICBjb25zdCB4ID0gdHlwZW9mIHAueCA9PT0gIm51bWJlciIgPyBwLnggOiBOdW1iZXIocC54KTsKICBjb25zdCB5ID0gdHlwZW9mIHAueSA9PT0gIm51bWJlciIgPyBwLnkgOiBOdW1iZXIocC55KTsKICBjb25zdCBoYXNYWSA9IE51bWJlci5pc0Zpbml0ZSh4KSAmJiBOdW1iZXIuaXNGaW5pdGUoeSk7CgogIGlmIChzZWxlY3RvcikgewogICAgY29uc3QgcHQgPSBhd2FpdCBjaHJvbWUudGFicwogICAgICAuc2VuZE1lc3NhZ2UodGFiSWQsIHsgdHlwZTogIlBPS0VfUkVTT0xWRV9DTElDS19QT0lOVCIsIHNlbGVjdG9yIH0pCiAgICAgIC5jYXRjaCgoZSkgPT4gewogICAgICAgIHRocm93IG5ldyBFcnJvcihgY2xpY2tfZWxlbWVudCByZXNvbHZlIGZhaWxlZDogJHtTdHJpbmcoZSl9YCk7CiAgICAgIH0pOwogICAgaWYgKAogICAgICAhcHQgfHwKICAgICAgcHQuc3VjY2VzcyAhPT0gdHJ1ZSB8fAogICAgICB0eXBlb2YgcHQueCAhPT0gIm51bWJlciIgfHwKICAgICAgdHlwZW9mIHB0LnkgIT09ICJudW1iZXIiIHx8CiAgICAgICFOdW1iZXIuaXNGaW5pdGUocHQueCkgfHwKICAgICAgIU51bWJlci5pc0Zpbml0ZShwdC55KQogICAgKSB7CiAgICAgIGNvbnN0IGVyciA9IHB0ICYmIHR5cGVvZiBwdC5lcnJvciA9PT0gInN0cmluZyIgPyBwdC5lcnJvciA6ICJjb3VsZCBub3QgcmVzb2x2ZSB0YXJnZXQgY29vcmRpbmF0ZXMiOwogICAgICB0aHJvdyBuZXcgRXJyb3IoYGNsaWNrX2VsZW1lbnQgJHtlcnJ9YCk7CiAgICB9CiAgICBhd2FpdCBzaG93Q3Vyc29yRmVlZGJhY2tEb3QodGFiSWQsIHB0LngsIHB0LnkpOwogICAgYXdhaXQgZGVidWdnZXJBdHRhY2hGb3JUb29sKHRhYklkKTsKICAgIHRyeSB7CiAgICAgIGF3YWl0IGRlYnVnZ2VyU2VuZCh0YWJJZCwgIklucHV0LmRpc3BhdGNoTW91c2VFdmVudCIsIHsKICAgICAgICB0eXBlOiAibW91c2VNb3ZlZCIsCiAgICAgICAgeDogcHQueCwKICAgICAgICB5OiBwdC55LAogICAgICB9KTsKICAgICAgYXdhaXQgbmV3IFByb21pc2UoKHIpID0+IHNldFRpbWVvdXQociwgQ0xJQ0tfRUxFTUVOVF9IT1ZFUl9ERUxBWV9NUykpOwogICAgfSBmaW5hbGx5IHsKICAgICAgYXdhaXQgZGVidWdnZXJEZXRhY2hGb3JUb29sKHRhYklkKTsKICAgIH0KICAgIGNvbnN0IHJlcyA9IGF3YWl0IGNocm9tZS50YWJzLnNlbmRNZXNzYWdlKHRhYklkLCB7IHR5cGU6ICJQT0tFX0NMSUNLX0VMRU1FTlQiLCBzZWxlY3RvciB9KS5jYXRjaCgoZSkgPT4gewogICAgICB0aHJvdyBuZXcgRXJyb3IoYGNsaWNrX2VsZW1lbnQgcmVsYXkgZmFpbGVkOiAke1N0cmluZyhlKX1gKTsKICAgIH0pOwogICAgcmV0dXJuIHdpdGhUYWJNZXRhKHRhYklkLCByZXMpOwogIH0KICBpZiAoaGFzWFkpIHsKICAgIGF3YWl0IHNob3dDdXJzb3JGZWVkYmFja0RvdCh0YWJJZCwgeCwgeSk7CiAgICBjb25zdCByID0gYXdhaXQgY2xpY2tWaWFEZWJ1Z2dlcih0YWJJZCwgeCwgeSk7CiAgICByZXR1cm4gd2l0aFRhYk1ldGEodGFiSWQsIHIpOwogIH0KICB0aHJvdyBuZXcgRXJyb3IoImNsaWNrX2VsZW1lbnQgcmVxdWlyZXMgc2VsZWN0b3Igb3IgbnVtZXJpYyB4IGFuZCB5Iik7Cn0KCi8qKiBAcGFyYW0ge3Vua25vd259IHBheWxvYWQgKi8KYXN5bmMgZnVuY3Rpb24gaGFuZGxlVHlwZVRleHQocGF5bG9hZCkgewogIGNvbnN0IHAgPSBhc1BheWxvYWQocGF5bG9hZCk7CiAgY29uc3QgdGV4dCA9IHR5cGVvZiBwLnRleHQgPT09ICJzdHJpbmciID8gcC50ZXh0IDogIiI7CiAgY29uc3QgdGFiSWQgPSBhd2FpdCByZXNvbHZlVGFiSWQodHlwZW9mIHAudGFiSWQgPT09ICJudW1iZXIiID8gcC50YWJJZCA6IHVuZGVmaW5lZCk7CiAgY29uc3Qgc2VsZWN0b3IgPSB0eXBlb2YgcC5zZWxlY3RvciA9PT0gInN0cmluZyIgPyBwLnNlbGVjdG9yIDogdW5kZWZpbmVkOwogIGNvbnN0IHNob3VsZENsZWFyID0gcC5jbGVhciAhPT0gZmFsc2U7CiAgY29uc3QgdHggPSB0eXBlb2YgcC54ID09PSAibnVtYmVyIiA/IHAueCA6IE51bWJlcihwLngpOwogIGNvbnN0IHR5ID0gdHlwZW9mIHAueSA9PT0gIm51bWJlciIgPyBwLnkgOiBOdW1iZXIocC55KTsKICBjb25zdCBoYXNYWSA9IE51bWJlci5pc0Zpbml0ZSh0eCkgJiYgTnVtYmVyLmlzRmluaXRlKHR5KTsKICBpZiAoaGFzWFkpIGF3YWl0IHNob3dDdXJzb3JGZWVkYmFja0RvdCh0YWJJZCwgdHgsIHR5KTsKCiAgY29uc3QgcmVzID0gYXdhaXQgY2hyb21lLnRhYnMKICAgIC5zZW5kTWVzc2FnZSh0YWJJZCwgewogICAgICB0eXBlOiAiUE9LRV9UWVBFX1RFWFQiLAogICAgICB0ZXh0LAogICAgICBzZWxlY3RvciwKICAgICAgY2xlYXI6IHNob3VsZENsZWFyLAogICAgfSkKICAgIC5jYXRjaCgoKSA9PiBudWxsKTsKCiAgaWYgKHJlcyAmJiByZXMuc3VjY2VzcyA9PT0gdHJ1ZSkgewogICAgcmV0dXJuIHdpdGhUYWJNZXRhKHRhYklkLCB7CiAgICAgIHN1Y2Nlc3M6IHRydWUsCiAgICAgIGNoYXJzVHlwZWQ6IHR5cGVvZiByZXMuY2hhcnNUeXBlZCA9PT0gIm51bWJlciIgPyByZXMuY2hhcnNUeXBlZCA6IHRleHQubGVuZ3RoLAogICAgfSk7CiAgfQogIGNvbnN0IGRiZyA9IGF3YWl0IHR5cGVUZXh0VmlhRGVidWdnZXIodGFiSWQsIHRleHQsIHNob3VsZENsZWFyKTsKICByZXR1cm4gd2l0aFRhYk1ldGEodGFiSWQsIGRiZyk7Cn0KCi8qKgogKiBNYWluLXdvcmxkIHNjcm9sbCBpbXBsZW1lbnRhdGlvbiBpbmplY3RlZCB2aWEgY2hyb21lLnNjcmlwdGluZyAoZ3VhcmFudGVlcyB0aGUgdGFyZ2V0IHRhYiBmcmFtZSkuCiAqIE11c3QgYmUgc2VsZi1jb250YWluZWQg4oCUIENocm9tZSBzZXJpYWxpemVzIHRoaXMgZnVuY3Rpb24gaW50byB0aGUgcGFnZS4KICogQHBhcmFtIHtSZWNvcmQ8c3RyaW5nLCB1bmtub3duPn0gcAogKi8KZnVuY3Rpb24gcG9rZUluamVjdGVkU2Nyb2xsV2luZG93KHApIHsKICBjb25zdCBiZWhhdmlvciA9IHAuYmVoYXZpb3IgPT09ICJzbW9vdGgiID8gInNtb290aCIgOiAiYXV0byI7CiAgY29uc3Qgc2VsZWN0b3IgPSB0eXBlb2YgcC5zZWxlY3RvciA9PT0gInN0cmluZyIgPyBwLnNlbGVjdG9yLnRyaW0oKSA6ICIiOwoKICAvKioKICAgKiBAcGFyYW0ge3N0cmluZ30gcwogICAqIEByZXR1cm5zIHtFbGVtZW50IHwgbnVsbH0KICAgKi8KICBmdW5jdGlvbiBxdWVyeVNlbGVjdG9yT3JYUGF0aChzKSB7CiAgICBjb25zdCB0ID0gcy50cmltKCk7CiAgICBpZiAodC5zdGFydHNXaXRoKCIvLyIpIHx8IHQudG9Mb3dlckNhc2UoKS5zdGFydHNXaXRoKCJ4cGF0aDoiKSkgewogICAgICBjb25zdCBleHByID0gdC50b0xvd2VyQ2FzZSgpLnN0YXJ0c1dpdGgoInhwYXRoOiIpID8gdC5zbGljZSg2KS50cmltKCkgOiB0OwogICAgICB0cnkgewogICAgICAgIGNvbnN0IHIgPSBkb2N1bWVudC5ldmFsdWF0ZShleHByLCBkb2N1bWVudCwgbnVsbCwgWFBhdGhSZXN1bHQuRklSU1RfT1JERVJFRF9OT0RFX1RZUEUsIG51bGwpOwogICAgICAgIGNvbnN0IG5vZGUgPSByLnNpbmdsZU5vZGVWYWx1ZTsKICAgICAgICByZXR1cm4gbm9kZSBpbnN0YW5jZW9mIEVsZW1lbnQgPyBub2RlIDogbnVsbDsKICAgICAgfSBjYXRjaCB7CiAgICAgICAgcmV0dXJuIG51bGw7CiAgICAgIH0KICAgIH0KICAgIHRyeSB7CiAgICAgIHJldHVybiBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKHQpOwogICAgfSBjYXRjaCB7CiAgICAgIHJldHVybiBudWxsOwogICAgfQogIH0KCiAgY29uc3QgZGlyUmF3ID0gdHlwZW9mIHAuZGlyZWN0aW9uID09PSAic3RyaW5nIiA/IHAuZGlyZWN0aW9uLnRvTG93ZXJDYXNlKCkgOiAiIjsKICBjb25zdCBkaXIgPQogICAgZGlyUmF3ID09PSAidXAiIHx8IGRpclJhdyA9PT0gImRvd24iIHx8IGRpclJhdyA9PT0gImxlZnQiIHx8IGRpclJhdyA9PT0gInJpZ2h0IiA/IGRpclJhdyA6ICIiOwoKICB0cnkgewogICAgaWYgKHNlbGVjdG9yKSB7CiAgICAgIGNvbnN0IGVsID0gcXVlcnlTZWxlY3Rvck9yWFBhdGgoc2VsZWN0b3IpOwogICAgICBpZiAoIWVsKSB7CiAgICAgICAgcmV0dXJuIHsgc3VjY2VzczogZmFsc2UsIHNjcm9sbFg6IHdpbmRvdy5zY3JvbGxYLCBzY3JvbGxZOiB3aW5kb3cuc2Nyb2xsWSwgZXJyb3I6ICJFbGVtZW50IG5vdCBmb3VuZCIgfTsKICAgICAgfQogICAgICBlbC5zY3JvbGxJbnRvVmlldyh7IGJlaGF2aW9yLCBibG9jazogImNlbnRlciIsIGlubGluZTogIm5lYXJlc3QiIH0pOwogICAgICByZXR1cm4geyBzdWNjZXNzOiB0cnVlLCBzY3JvbGxYOiB3aW5kb3cuc2Nyb2xsWCwgc2Nyb2xsWTogd2luZG93LnNjcm9sbFkgfTsKICAgIH0KCiAgICBpZiAodHlwZW9mIHAueCA9PT0gIm51bWJlciIgfHwgdHlwZW9mIHAueSA9PT0gIm51bWJlciIpIHsKICAgICAgY29uc3QgbGVmdCA9IHR5cGVvZiBwLnggPT09ICJudW1iZXIiID8gcC54IDogd2luZG93LnNjcm9sbFg7CiAgICAgIGNvbnN0IHRvcCA9IHR5cGVvZiBwLnkgPT09ICJudW1iZXIiID8gcC55IDogd2luZG93LnNjcm9sbFk7CiAgICAgIHdpbmRvdy5zY3JvbGxUbyh7IGxlZnQsIHRvcCwgYmVoYXZpb3IgfSk7CiAgICAgIHJldHVybiB7IHN1Y2Nlc3M6IHRydWUsIHNjcm9sbFg6IHdpbmRvdy5zY3JvbGxYLCBzY3JvbGxZOiB3aW5kb3cuc2Nyb2xsWSB9OwogICAgfQoKICAgIGxldCBkeCA9IHR5cGVvZiBwLmRlbHRhWCA9PT0gIm51bWJlciIgJiYgTnVtYmVyLmlzRmluaXRlKHAuZGVsdGFYKSA/IHAuZGVsdGFYIDogMDsKICAgIGxldCBkeSA9IHR5cGVvZiBwLmRlbHRhWSA9PT0gIm51bWJlciIgJiYgTnVtYmVyLmlzRmluaXRlKHAuZGVsdGFZKSA/IHAuZGVsdGFZIDogMDsKCiAgICBpZiAoZGlyKSB7CiAgICAgIGxldCBhbXQgPSB0eXBlb2YgcC5hbW91bnQgPT09ICJudW1iZXIiICYmIE51bWJlci5pc0Zpbml0ZShwLmFtb3VudCkgPyBNYXRoLmFicyhwLmFtb3VudCkgOiBOYU47CiAgICAgIGlmICghTnVtYmVyLmlzRmluaXRlKGFtdCkgfHwgYW10ID09PSAwKSB7CiAgICAgICAgaWYgKGRpciA9PT0gInVwIiB8fCBkaXIgPT09ICJkb3duIikgewogICAgICAgICAgY29uc3QgZnJvbURlbHRhID0gdHlwZW9mIHAuZGVsdGFZID09PSAibnVtYmVyIiAmJiBOdW1iZXIuaXNGaW5pdGUocC5kZWx0YVkpICYmIHAuZGVsdGFZICE9PSAwOwogICAgICAgICAgYW10ID0gZnJvbURlbHRhID8gTWF0aC5hYnMocC5kZWx0YVkpIDogTWF0aC5tYXgoMjAwLCBNYXRoLmZsb29yKHdpbmRvdy5pbm5lckhlaWdodCAqIDAuODUpKTsKICAgICAgICB9IGVsc2UgewogICAgICAgICAgY29uc3QgZnJvbURlbHRhID0gdHlwZW9mIHAuZGVsdGFYID09PSAibnVtYmVyIiAmJiBOdW1iZXIuaXNGaW5pdGUocC5kZWx0YVgpICYmIHAuZGVsdGFYICE9PSAwOwogICAgICAgICAgYW10ID0gZnJvbURlbHRhID8gTWF0aC5hYnMocC5kZWx0YVgpIDogTWF0aC5tYXgoMjAwLCBNYXRoLmZsb29yKHdpbmRvdy5pbm5lcldpZHRoICogMC44NSkpOwogICAgICAgIH0KICAgICAgfQogICAgICBkeCA9IGRpciA9PT0gImxlZnQiID8gLWFtdCA6IGRpciA9PT0gInJpZ2h0IiA/IGFtdCA6IDA7CiAgICAgIGR5ID0gZGlyID09PSAidXAiID8gLWFtdCA6IGRpciA9PT0gImRvd24iID8gYW10IDogMDsKICAgIH0KCiAgICB3aW5kb3cuc2Nyb2xsQnkoeyBsZWZ0OiBkeCwgdG9wOiBkeSwgYmVoYXZpb3IgfSk7CiAgICByZXR1cm4geyBzdWNjZXNzOiB0cnVlLCBzY3JvbGxYOiB3aW5kb3cuc2Nyb2xsWCwgc2Nyb2xsWTogd2luZG93LnNjcm9sbFkgfTsKICB9IGNhdGNoIChlcnIpIHsKICAgIHJldHVybiB7IHN1Y2Nlc3M6IGZhbHNlLCBzY3JvbGxYOiB3aW5kb3cuc2Nyb2xsWCwgc2Nyb2xsWTogd2luZG93LnNjcm9sbFksIGVycm9yOiBTdHJpbmcoZXJyKSB9OwogIH0KfQoKLyoqIEBwYXJhbSB7dW5rbm93bn0gcGF5bG9hZCAqLwphc3luYyBmdW5jdGlvbiBoYW5kbGVTY3JvbGxXaW5kb3cocGF5bG9hZCkgewogIGNvbnN0IHAgPSBhc1BheWxvYWQocGF5bG9hZCk7CiAgY29uc3QgdGFiSWQgPSBhd2FpdCByZXNvbHZlVGFiSWQodHlwZW9mIHAudGFiSWQgPT09ICJudW1iZXIiID8gcC50YWJJZCA6IHVuZGVmaW5lZCk7CiAgLyoqIFByZWZlciBzY3JpcHRpbmcuZXhlY3V0ZVNjcmlwdCBzbyBzY3JvbGwgcnVucyBpbiB0aGUgdGFiJ3MgbWFpbiBmcmFtZSAobm90IGV4dGVuc2lvbi9vZmZzY3JlZW4gY29udGV4dHMpLiAqLwogIGNvbnN0IHJlc3VsdHMgPSBhd2FpdCBjaHJvbWUuc2NyaXB0aW5nLmV4ZWN1dGVTY3JpcHQoewogICAgdGFyZ2V0OiB7IHRhYklkLCBhbGxGcmFtZXM6IGZhbHNlIH0sCiAgICB3b3JsZDogIk1BSU4iLAogICAgaW5qZWN0SW1tZWRpYXRlbHk6IHRydWUsCiAgICBmdW5jOiBwb2tlSW5qZWN0ZWRTY3JvbGxXaW5kb3csCiAgICBhcmdzOiBbcF0sCiAgfSk7CiAgY29uc3QgcmVzID0gLyoqIEB0eXBlIHt1bmtub3dufSAqLyAocmVzdWx0c1swXT8ucmVzdWx0KTsKICBpZiAocmVzID09PSB1bmRlZmluZWQpIHsKICAgIHRocm93IG5ldyBFcnJvcigic2Nyb2xsX3dpbmRvdzogbm8gcmVzdWx0IGZyb20gZXhlY3V0ZVNjcmlwdCAodGFiIG1heSBiZSByZXN0cmljdGVkIG9yIHVuYXZhaWxhYmxlKSIpOwogIH0KICByZXR1cm4gd2l0aFRhYk1ldGEodGFiSWQsIHJlcyk7Cn0KCi8qKiBAcGFyYW0ge3Vua25vd259IHBheWxvYWQgKi8KYXN5bmMgZnVuY3Rpb24gaGFuZGxlU2NyZWVuc2hvdChwYXlsb2FkKSB7CiAgY29uc3QgcCA9IGFzUGF5bG9hZChwYXlsb2FkKTsKICBjb25zdCB0YWJJZCA9IGF3YWl0IHJlc29sdmVUYWJJZCh0eXBlb2YgcC50YWJJZCA9PT0gIm51bWJlciIgPyBwLnRhYklkIDogdW5kZWZpbmVkKTsKICBjb25zdCB0YWIgPSBhd2FpdCBlbnN1cmVUYWJWaXNpYmxlRm9yQ2FwdHVyZSh0YWJJZCk7CiAgY29uc3QgZm10ID0gcC5mb3JtYXQgPT09ICJqcGVnIiA/ICJqcGVnIiA6ICJwbmciOwogIGNvbnN0IHJhd1EgPSB0eXBlb2YgcC5xdWFsaXR5ID09PSAibnVtYmVyIiA/IHAucXVhbGl0eSA6IDg1OwogIC8qKiBAdHlwZSB7eyBmb3JtYXQ6ICdwbmcnIHwgJ2pwZWcnLCBxdWFsaXR5PzogbnVtYmVyIH19ICovCiAgY29uc3Qgb3B0cyA9CiAgICBmbXQgPT09ICJqcGVnIgogICAgICA/IHsgZm9ybWF0OiAianBlZyIsIHF1YWxpdHk6IE1hdGgubWluKDEwMCwgTWF0aC5tYXgoMCwgcmF3USkpIH0KICAgICAgOiB7IGZvcm1hdDogInBuZyIgfTsKICBjb25zdCBkYXRhVXJsID0gYXdhaXQgY2hyb21lLnRhYnMuY2FwdHVyZVZpc2libGVUYWIodGFiLndpbmRvd0lkLCBvcHRzKTsKICBjb25zdCBtID0gL15kYXRhOihbXjtdKyk7YmFzZTY0LCguKykkLy5leGVjKGRhdGFVcmwpOwogIGlmICghbSkgdGhyb3cgbmV3IEVycm9yKCJJbnZhbGlkIHNjcmVlbnNob3QgZGF0YSBmcm9tIGJyb3dzZXIiKTsKICByZXR1cm4gd2l0aFRhYk1ldGEodGFiSWQsIHsKICAgIHR5cGU6ICJzY3JlZW5zaG90X3Jlc3VsdCIsCiAgICBkYXRhOiBtWzJdLAogICAgbWltZVR5cGU6IG1bMV0sCiAgfSk7Cn0KCi8qKiBAcGFyYW0ge3Vua25vd259IHBheWxvYWQgKi8KYXN5bmMgZnVuY3Rpb24gaGFuZGxlRXJyb3JSZXBvcnRlcihwYXlsb2FkKSB7CiAgY29uc3QgcCA9IGFzUGF5bG9hZChwYXlsb2FkKTsKICBjb25zdCB0YWJJZCA9IGF3YWl0IHJlc29sdmVUYWJJZCh0eXBlb2YgcC50YWJJZCA9PT0gIm51bWJlciIgPyBwLnRhYklkIDogdW5kZWZpbmVkKTsKICBjb25zdCBsaW1pdCA9IHR5cGVvZiBwLmxpbWl0ID09PSAibnVtYmVyIiA/IHAubGltaXQgOiA1MDsKICBjb25zdCByZXMgPSBhd2FpdCBjaHJvbWUudGFicwogICAgLnNlbmRNZXNzYWdlKHRhYklkLCB7IHR5cGU6ICJQT0tFX0dFVF9QQUdFX0VSUk9SUyIsIGxpbWl0IH0pCiAgICAuY2F0Y2goKGUpID0+IHsKICAgICAgdGhyb3cgbmV3IEVycm9yKGBlcnJvcl9yZXBvcnRlciByZWxheSBmYWlsZWQ6ICR7U3RyaW5nKGUpfWApOwogICAgfSk7CiAgcmV0dXJuIHdpdGhUYWJNZXRhKHRhYklkLCByZXMpOwp9CgovKiogQHBhcmFtIHt1bmtub3dufSBwYXlsb2FkICovCmFzeW5jIGZ1bmN0aW9uIGhhbmRsZUdldFBlcmZvcm1hbmNlTWV0cmljcyhwYXlsb2FkKSB7CiAgY29uc3QgcCA9IGFzUGF5bG9hZChwYXlsb2FkKTsKICBjb25zdCB0YWJJZCA9IGF3YWl0IHJlc29sdmVUYWJJZCh0eXBlb2YgcC50YWJJZCA9PT0gIm51bWJlciIgPyBwLnRhYklkIDogdW5kZWZpbmVkKTsKICBhd2FpdCBkZWJ1Z2dlckF0dGFjaEZvclRvb2wodGFiSWQpOwogIHRyeSB7CiAgICBjb25zdCByYXdNZXRyaWNzID0gYXdhaXQgZGVidWdnZXJTZW5kV2l0aFJlc3VsdCh0YWJJZCwgIlBlcmZvcm1hbmNlLmdldE1ldHJpY3MiLCB7fSk7CiAgICBjb25zdCBtZXRyaWNzQXJyID0gQXJyYXkuaXNBcnJheShyYXdNZXRyaWNzKQogICAgICA/IHJhd01ldHJpY3MKICAgICAgOiByYXdNZXRyaWNzICYmIHR5cGVvZiByYXdNZXRyaWNzID09PSAib2JqZWN0IiAmJiAibWV0cmljcyIgaW4gcmF3TWV0cmljcwogICAgICAgID8gLyoqIEB0eXBlIHt7IG1ldHJpY3M/OiB1bmtub3duIH19ICovIChyYXdNZXRyaWNzKS5tZXRyaWNzCiAgICAgICAgOiBudWxsOwogICAgLyoqCiAgICAgKiBAcGFyYW0ge3N0cmluZ30gbmFtZQogICAgICovCiAgICBjb25zdCBieSA9IChuYW1lKSA9PiB7CiAgICAgIGlmICghQXJyYXkuaXNBcnJheShtZXRyaWNzQXJyKSkgcmV0dXJuIHVuZGVmaW5lZDsKICAgICAgY29uc3Qgcm93ID0gbWV0cmljc0Fyci5maW5kKAogICAgICAgICh4KSA9PiB4ICYmIHR5cGVvZiB4ID09PSAib2JqZWN0IiAmJiAvKiogQHR5cGUge3sgbmFtZT86IHN0cmluZyB9fSAqLyAoeCkubmFtZSA9PT0gbmFtZSwKICAgICAgKTsKICAgICAgcmV0dXJuIHJvdyAmJiB0eXBlb2YgLyoqIEB0eXBlIHt7IHZhbHVlPzogbnVtYmVyIH19ICovIChyb3cpLnZhbHVlID09PSAibnVtYmVyIgogICAgICAgID8gLyoqIEB0eXBlIHt7IHZhbHVlOiBudW1iZXIgfX0gKi8gKHJvdykudmFsdWUKICAgICAgICA6IHVuZGVmaW5lZDsKICAgIH07CgogICAgY29uc3QgbmF2RXhwciA9IGAoKCkgPT4gewogICAgICBjb25zdCB0ID0gcGVyZm9ybWFuY2UudGltaW5nOwogICAgICBjb25zdCBucyA9IHQubmF2aWdhdGlvblN0YXJ0IHx8IDA7CiAgICAgIGlmICghbnMpIHJldHVybiB7IGRvbUNvbnRlbnRMb2FkZWQ6IG51bGwsIGxvYWRFdmVudEVuZDogbnVsbCB9OwogICAgICByZXR1cm4gewogICAgICAgIGRvbUNvbnRlbnRMb2FkZWQ6IHQuZG9tQ29udGVudExvYWRlZEV2ZW50RW5kID4gMCA/IHQuZG9tQ29udGVudExvYWRlZEV2ZW50RW5kIC0gbnMgOiBudWxsLAogICAgICAgIGxvYWRFdmVudEVuZDogdC5sb2FkRXZlbnRFbmQgPiAwID8gdC5sb2FkRXZlbnRFbmQgLSBucyA6IG51bGwsCiAgICAgIH07CiAgICB9KSgpYDsKICAgIGNvbnN0IG5hdlJlcyA9IGF3YWl0IGRlYnVnZ2VyU2VuZFdpdGhSZXN1bHQodGFiSWQsICJSdW50aW1lLmV2YWx1YXRlIiwgewogICAgICBleHByZXNzaW9uOiBuYXZFeHByLAogICAgICByZXR1cm5CeVZhbHVlOiB0cnVlLAogICAgfSk7CiAgICBjb25zdCBuYXZWYWwgPQogICAgICBuYXZSZXMgJiYgdHlwZW9mIG5hdlJlcyA9PT0gIm9iamVjdCIgJiYgInJlc3VsdCIgaW4gbmF2UmVzCiAgICAgICAgPyAvKiogQHR5cGUge3sgcmVzdWx0PzogeyB2YWx1ZT86IHVua25vd24gfSB9fSAqLyAobmF2UmVzKS5yZXN1bHQ/LnZhbHVlCiAgICAgICAgOiB1bmRlZmluZWQ7CgogICAgY29uc3QgcGFpbnRFeHByID0gYCgoKSA9PiB7CiAgICAgIGNvbnN0IGVudHJpZXMgPSBwZXJmb3JtYW5jZS5nZXRFbnRyaWVzQnlUeXBlKCJwYWludCIpOwogICAgICBsZXQgZmlyc3RQYWludCA9IG51bGw7CiAgICAgIGxldCBmaXJzdENvbnRlbnRmdWxQYWludCA9IG51bGw7CiAgICAgIGZvciAoY29uc3QgZSBvZiBlbnRyaWVzKSB7CiAgICAgICAgaWYgKGUubmFtZSA9PT0gImZpcnN0LXBhaW50IikgZmlyc3RQYWludCA9IGUuc3RhcnRUaW1lOwogICAgICAgIGlmIChlLm5hbWUgPT09ICJmaXJzdC1jb250ZW50ZnVsLXBhaW50IikgZmlyc3RDb250ZW50ZnVsUGFpbnQgPSBlLnN0YXJ0VGltZTsKICAgICAgfQogICAgICByZXR1cm4geyBmaXJzdFBhaW50LCBmaXJzdENvbnRlbnRmdWxQYWludCB9OwogICAgfSkoKWA7CiAgICBjb25zdCBwYWludFJlcyA9IGF3YWl0IGRlYnVnZ2VyU2VuZFdpdGhSZXN1bHQodGFiSWQsICJSdW50aW1lLmV2YWx1YXRlIiwgewogICAgICBleHByZXNzaW9uOiBwYWludEV4cHIsCiAgICAgIHJldHVybkJ5VmFsdWU6IHRydWUsCiAgICB9KTsKICAgIGNvbnN0IHBhaW50VmFsID0KICAgICAgcGFpbnRSZXMgJiYgdHlwZW9mIHBhaW50UmVzID09PSAib2JqZWN0IiAmJiAicmVzdWx0IiBpbiBwYWludFJlcwogICAgICAgID8gLyoqIEB0eXBlIHt7IHJlc3VsdD86IHsgdmFsdWU/OiB1bmtub3duIH0gfX0gKi8gKHBhaW50UmVzKS5yZXN1bHQ/LnZhbHVlCiAgICAgICAgOiB1bmRlZmluZWQ7CgogICAgY29uc3QgbnYgPSBuYXZWYWwgJiYgdHlwZW9mIG5hdlZhbCA9PT0gIm9iamVjdCIgPyAvKiogQHR5cGUge1JlY29yZDxzdHJpbmcsIHVua25vd24+fSAqLyAobmF2VmFsKSA6IHt9OwogICAgY29uc3QgcHYgPSBwYWludFZhbCAmJiB0eXBlb2YgcGFpbnRWYWwgPT09ICJvYmplY3QiID8gLyoqIEB0eXBlIHtSZWNvcmQ8c3RyaW5nLCB1bmtub3duPn0gKi8gKHBhaW50VmFsKSA6IHt9OwoKICAgIHJldHVybiB3aXRoVGFiTWV0YSh0YWJJZCwgewogICAgICBkb21Db250ZW50TG9hZGVkOiBudi5kb21Db250ZW50TG9hZGVkID8/IG51bGwsCiAgICAgIGxvYWRFdmVudEVuZDogbnYubG9hZEV2ZW50RW5kID8/IG51bGwsCiAgICAgIGZpcnN0UGFpbnQ6IHB2LmZpcnN0UGFpbnQgPz8gbnVsbCwKICAgICAgZmlyc3RDb250ZW50ZnVsUGFpbnQ6IHB2LmZpcnN0Q29udGVudGZ1bFBhaW50ID8/IG51bGwsCiAgICAgIGpzSGVhcFVzZWQ6IGJ5KCJKU0hlYXBVc2VkU2l6ZSIpID8/IG51bGwsCiAgICAgIGpzSGVhcFRvdGFsOiBieSgiSlNIZWFwVG90YWxTaXplIikgPz8gbnVsbCwKICAgIH0pOwogIH0gZmluYWxseSB7CiAgICBhd2FpdCBkZWJ1Z2dlckRldGFjaEZvclRvb2wodGFiSWQpOwogIH0KfQoKLyoqCiAqIEBwYXJhbSB7QXJyYXlCdWZmZXJ9IGJ1ZmZlcgogKi8KZnVuY3Rpb24gYXJyYXlCdWZmZXJUb0Jhc2U2NChidWZmZXIpIHsKICBsZXQgYmluYXJ5ID0gIiI7CiAgY29uc3QgYnl0ZXMgPSBuZXcgVWludDhBcnJheShidWZmZXIpOwogIGNvbnN0IGNodW5rID0gMHg4MDAwOwogIGZvciAobGV0IGkgPSAwOyBpIDwgYnl0ZXMuYnl0ZUxlbmd0aDsgaSArPSBjaHVuaykgewogICAgYmluYXJ5ICs9IFN0cmluZy5mcm9tQ2hhckNvZGUuYXBwbHkobnVsbCwgLyoqIEB0eXBlIHtudW1iZXJbXX0gKi8gKEFycmF5LmZyb20oYnl0ZXMuc3ViYXJyYXkoaSwgaSArIGNodW5rKSkpKTsKICB9CiAgcmV0dXJuIGJ0b2EoYmluYXJ5KTsKfQoKLyoqCiAqIEBwYXJhbSB7c3RyaW5nW119IGRhdGFVcmxzCiAqLwphc3luYyBmdW5jdGlvbiBzdGl0Y2hGdWxsUGFnZVNjcmVlbnNob3RzKGRhdGFVcmxzKSB7CiAgaWYgKGRhdGFVcmxzLmxlbmd0aCA9PT0gMCkgdGhyb3cgbmV3IEVycm9yKCJmdWxsX3BhZ2VfY2FwdHVyZTogbm8gc3RyaXBzIik7CiAgLyoqIEB0eXBlIHtJbWFnZUJpdG1hcFtdfSAqLwogIGNvbnN0IGJpdG1hcHMgPSBbXTsKICB0cnkgewogICAgZm9yIChjb25zdCB1IG9mIGRhdGFVcmxzKSB7CiAgICAgIGNvbnN0IHJlcyA9IGF3YWl0IGZldGNoKHUpOwogICAgICBjb25zdCBibG9iID0gYXdhaXQgcmVzLmJsb2IoKTsKICAgICAgY29uc3QgYm0gPSBhd2FpdCBjcmVhdGVJbWFnZUJpdG1hcChibG9iKTsKICAgICAgYml0bWFwcy5wdXNoKGJtKTsKICAgIH0KICAgIGxldCB3aWR0aCA9IDA7CiAgICBsZXQgaGVpZ2h0ID0gMDsKICAgIGZvciAoY29uc3QgYm0gb2YgYml0bWFwcykgewogICAgICB3aWR0aCA9IE1hdGgubWF4KHdpZHRoLCBibS53aWR0aCk7CiAgICAgIGhlaWdodCArPSBibS5oZWlnaHQ7CiAgICB9CiAgICBjb25zdCBjYW52YXMgPSBuZXcgT2Zmc2NyZWVuQ2FudmFzKHdpZHRoLCBoZWlnaHQpOwogICAgY29uc3QgY3R4ID0gY2FudmFzLmdldENvbnRleHQoIjJkIik7CiAgICBpZiAoIWN0eCkgdGhyb3cgbmV3IEVycm9yKCJmdWxsX3BhZ2VfY2FwdHVyZTogbm8gMmQgY29udGV4dCIpOwogICAgbGV0IHkgPSAwOwogICAgZm9yIChjb25zdCBibSBvZiBiaXRtYXBzKSB7CiAgICAgIGN0eC5kcmF3SW1hZ2UoYm0sIDAsIHkpOwogICAgICB5ICs9IGJtLmhlaWdodDsKICAgIH0KICAgIGNvbnN0IG1pbWVUeXBlID0gU3RyaW5nKGRhdGFVcmxzWzBdKS5zdGFydHNXaXRoKCJkYXRhOmltYWdlL2pwZWciKSA/ICJpbWFnZS9qcGVnIiA6ICJpbWFnZS9wbmciOwogICAgY29uc3QgYmxvYiA9IGF3YWl0IGNhbnZhcy5jb252ZXJ0VG9CbG9iKHsgdHlwZTogbWltZVR5cGUgfSk7CiAgICBjb25zdCBidWYgPSBhd2FpdCBibG9iLmFycmF5QnVmZmVyKCk7CiAgICBjb25zdCBiNjQgPSBhcnJheUJ1ZmZlclRvQmFzZTY0KGJ1Zik7CiAgICByZXR1cm4gYGRhdGE6JHttaW1lVHlwZX07YmFzZTY0LCR7YjY0fWA7CiAgfSBmaW5hbGx5IHsKICAgIGZvciAoY29uc3QgYm0gb2YgYml0bWFwcykgewogICAgICB0cnkgewogICAgICAgIGJtLmNsb3NlKCk7CiAgICAgIH0gY2F0Y2ggewogICAgICAgIC8qIGlnbm9yZSAqLwogICAgICB9CiAgICB9CiAgfQp9CgovKiogQHBhcmFtIHt1bmtub3dufSBwYXlsb2FkICovCmFzeW5jIGZ1bmN0aW9uIGhhbmRsZUZ1bGxQYWdlQ2FwdHVyZShwYXlsb2FkKSB7CiAgY29uc3QgcCA9IGFzUGF5bG9hZChwYXlsb2FkKTsKICBjb25zdCB0YWJJZCA9IGF3YWl0IHJlc29sdmVUYWJJZCh0eXBlb2YgcC50YWJJZCA9PT0gIm51bWJlciIgPyBwLnRhYklkIDogdW5kZWZpbmVkKTsKICBjb25zdCB0YWIgPSBhd2FpdCBlbnN1cmVUYWJWaXNpYmxlRm9yQ2FwdHVyZSh0YWJJZCk7CiAgY29uc3QgZm10ID0gcC5mb3JtYXQgPT09ICJqcGVnIiA/ICJqcGVnIiA6ICJwbmciOwogIGNvbnN0IHJhd1EgPSB0eXBlb2YgcC5xdWFsaXR5ID09PSAibnVtYmVyIiA/IHAucXVhbGl0eSA6IDg1OwogIC8qKiBAdHlwZSB7eyBmb3JtYXQ6ICdwbmcnIHwgJ2pwZWcnLCBxdWFsaXR5PzogbnVtYmVyIH19ICovCiAgY29uc3Qgb3B0cyA9CiAgICBmbXQgPT09ICJqcGVnIgogICAgICA/IHsgZm9ybWF0OiAianBlZyIsIHF1YWxpdHk6IE1hdGgubWluKDEwMCwgTWF0aC5tYXgoMCwgcmF3USkpIH0KICAgICAgOiB7IGZvcm1hdDogInBuZyIgfTsKCiAgY29uc3QgaW5mbyA9IGF3YWl0IGNocm9tZS50YWJzLnNlbmRNZXNzYWdlKHRhYklkLCB7IHR5cGU6ICJQT0tFX0dFVF9TQ1JPTExfSU5GTyIgfSkuY2F0Y2goKCkgPT4gbnVsbCk7CiAgaWYgKCFpbmZvIHx8IHR5cGVvZiBpbmZvICE9PSAib2JqZWN0IiB8fCB0eXBlb2YgLyoqIEB0eXBlIHt7IHNjcm9sbEhlaWdodD86IHVua25vd24gfX0gKi8gKGluZm8pLnNjcm9sbEhlaWdodCAhPT0gIm51bWJlciIpIHsKICAgIHRocm93IG5ldyBFcnJvcigiZnVsbF9wYWdlX2NhcHR1cmU6IGNvbnRlbnQgc2NyaXB0IHVuYXZhaWxhYmxlIG9yIGludmFsaWQgc2Nyb2xsIGluZm8iKTsKICB9CiAgY29uc3Qgc2Nyb2xsSGVpZ2h0ID0gLyoqIEB0eXBlIHt7IHNjcm9sbEhlaWdodDogbnVtYmVyOyBpbm5lckhlaWdodD86IG51bWJlciB9fSAqLyAoaW5mbykuc2Nyb2xsSGVpZ2h0OwogIGNvbnN0IHZoID0gTWF0aC5tYXgoMSwgTWF0aC5mbG9vcigvKiogQHR5cGUge3sgaW5uZXJIZWlnaHQ/OiBudW1iZXIgfX0gKi8gKGluZm8pLmlubmVySGVpZ2h0IHx8IDYwMCkpOwoKICAvKiogQHR5cGUge3N0cmluZ1tdfSAqLwogIGNvbnN0IGRhdGFVcmxzID0gW107CiAgYXdhaXQgY2hyb21lLnRhYnMuc2VuZE1lc3NhZ2UodGFiSWQsIHsgdHlwZTogIlBPS0VfU0NST0xMX1RPIiwgeTogMCB9KTsKICBhd2FpdCBuZXcgUHJvbWlzZSgocikgPT4gc2V0VGltZW91dChyLCAxMDApKTsKCiAgbGV0IHkgPSAwOwogIGZvciAoOzspIHsKICAgIGNvbnN0IGRhdGFVcmwgPSBhd2FpdCBjaHJvbWUudGFicy5jYXB0dXJlVmlzaWJsZVRhYih0YWIud2luZG93SWQsIG9wdHMpOwogICAgZGF0YVVybHMucHVzaChkYXRhVXJsKTsKICAgIGlmICh5ICsgdmggPj0gc2Nyb2xsSGVpZ2h0IC0gMikgYnJlYWs7CiAgICB5ID0gTWF0aC5taW4oeSArIHZoLCBNYXRoLm1heCgwLCBzY3JvbGxIZWlnaHQgLSB2aCkpOwogICAgYXdhaXQgY2hyb21lLnRhYnMuc2VuZE1lc3NhZ2UodGFiSWQsIHsgdHlwZTogIlBPS0VfU0NST0xMX1RPIiwgeSB9KTsKICAgIGF3YWl0IG5ldyBQcm9taXNlKChyKSA9PiBzZXRUaW1lb3V0KHIsIDEyMCkpOwogIH0KCiAgYXdhaXQgY2hyb21lLnRhYnMuc2VuZE1lc3NhZ2UodGFiSWQsIHsgdHlwZTogIlBPS0VfU0NST0xMX1RPIiwgeTogMCB9KTsKCiAgY29uc3Qgc3RpdGNoZWQgPSBhd2FpdCBzdGl0Y2hGdWxsUGFnZVNjcmVlbnNob3RzKGRhdGFVcmxzKTsKICBjb25zdCBtID0gL15kYXRhOihbXjtdKyk7YmFzZTY0LCguKykkLy5leGVjKHN0aXRjaGVkKTsKICBpZiAoIW0pIHRocm93IG5ldyBFcnJvcigiZnVsbF9wYWdlX2NhcHR1cmU6IGludmFsaWQgc3RpdGNoZWQgZGF0YSBVUkwiKTsKICByZXR1cm4gd2l0aFRhYk1ldGEodGFiSWQsIHsKICAgIHR5cGU6ICJzY3JlZW5zaG90X3Jlc3VsdCIsCiAgICBkYXRhOiBtWzJdLAogICAgbWltZVR5cGU6IG1bMV0sCiAgfSk7Cn0KCi8qKiBAcGFyYW0ge3Vua25vd259IHBheWxvYWQgKi8KYXN5bmMgZnVuY3Rpb24gaGFuZGxlUGRmRXhwb3J0KHBheWxvYWQpIHsKICBjb25zdCBwID0gYXNQYXlsb2FkKHBheWxvYWQpOwogIGNvbnN0IHRhYklkID0gYXdhaXQgcmVzb2x2ZVRhYklkKHR5cGVvZiBwLnRhYklkID09PSAibnVtYmVyIiA/IHAudGFiSWQgOiB1bmRlZmluZWQpOwogIGF3YWl0IGVuc3VyZVRhYlZpc2libGVGb3JDYXB0dXJlKHRhYklkKTsKICBhd2FpdCBkZWJ1Z2dlckF0dGFjaEZvclRvb2wodGFiSWQpOwogIHRyeSB7CiAgICBjb25zdCBzY2FsZSA9IHR5cGVvZiBwLnNjYWxlID09PSAibnVtYmVyIiAmJiBwLnNjYWxlID4gMCA/IHAuc2NhbGUgOiAxOwogICAgY29uc3QgcmVzID0gYXdhaXQgZGVidWdnZXJTZW5kV2l0aFJlc3VsdCh0YWJJZCwgIlBhZ2UucHJpbnRUb1BERiIsIHsKICAgICAgcHJpbnRCYWNrZ3JvdW5kOiB0cnVlLAogICAgICBsYW5kc2NhcGU6IHAubGFuZHNjYXBlID09PSB0cnVlLAogICAgICBzY2FsZSwKICAgIH0pOwogICAgY29uc3QgZGF0YSA9CiAgICAgIHJlcyAmJiB0eXBlb2YgcmVzID09PSAib2JqZWN0IiAmJiByZXMgIT09IG51bGwgJiYgImRhdGEiIGluIHJlcwogICAgICAgID8gU3RyaW5nKC8qKiBAdHlwZSB7eyBkYXRhPzogc3RyaW5nIH19ICovIChyZXMpLmRhdGEgPz8gIiIpCiAgICAgICAgOiAiIjsKICAgIGlmICghZGF0YSkgdGhyb3cgbmV3IEVycm9yKCJwZGZfZXhwb3J0OiBwcmludFRvUERGIHJldHVybmVkIG5vIGRhdGEiKTsKICAgIHJldHVybiB3aXRoVGFiTWV0YSh0YWJJZCwgeyBzdWNjZXNzOiB0cnVlLCBkYXRhLCBtaW1lVHlwZTogImFwcGxpY2F0aW9uL3BkZiIgfSk7CiAgfSBmaW5hbGx5IHsKICAgIGF3YWl0IGRlYnVnZ2VyRGV0YWNoRm9yVG9vbCh0YWJJZCk7CiAgfQp9Cgpjb25zdCBERVZJQ0VfUFJFU0VUUyA9IHsKICBtb2JpbGU6IHsgd2lkdGg6IDM5MCwgaGVpZ2h0OiA4NDQsIGRldmljZVNjYWxlRmFjdG9yOiAzLCBtb2JpbGU6IHRydWUgfSwKICB0YWJsZXQ6IHsgd2lkdGg6IDgzNCwgaGVpZ2h0OiAxMTEyLCBkZXZpY2VTY2FsZUZhY3RvcjogMiwgbW9iaWxlOiB0cnVlIH0sCiAgZGVza3RvcDogeyB3aWR0aDogMTI4MCwgaGVpZ2h0OiA4MDAsIGRldmljZVNjYWxlRmFjdG9yOiAxLCBtb2JpbGU6IGZhbHNlIH0sCn07CgovKiogQHBhcmFtIHt1bmtub3dufSBwYXlsb2FkICovCmFzeW5jIGZ1bmN0aW9uIGhhbmRsZURldmljZUVtdWxhdGUocGF5bG9hZCkgewogIGNvbnN0IHAgPSBhc1BheWxvYWQocGF5bG9hZCk7CiAgY29uc3QgdGFiSWQgPSBhd2FpdCByZXNvbHZlVGFiSWQodHlwZW9mIHAudGFiSWQgPT09ICJudW1iZXIiID8gcC50YWJJZCA6IHVuZGVmaW5lZCk7CiAgY29uc3QgZCA9IHAuZGV2aWNlID09PSAibW9iaWxlIiB8fCBwLmRldmljZSA9PT0gInRhYmxldCIgfHwgcC5kZXZpY2UgPT09ICJkZXNrdG9wIiA/IHAuZGV2aWNlIDogImRlc2t0b3AiOwogIGNvbnN0IHByZXNldCA9IERFVklDRV9QUkVTRVRTW2RdOwogIGNvbnN0IHdpZHRoID0gdHlwZW9mIHAud2lkdGggPT09ICJudW1iZXIiID8gcC53aWR0aCA6IHByZXNldC53aWR0aDsKICBjb25zdCBoZWlnaHQgPSB0eXBlb2YgcC5oZWlnaHQgPT09ICJudW1iZXIiID8gcC5oZWlnaHQgOiBwcmVzZXQuaGVpZ2h0OwogIGNvbnN0IGRldmljZVNjYWxlRmFjdG9yID0KICAgIHR5cGVvZiBwLmRldmljZVNjYWxlRmFjdG9yID09PSAibnVtYmVyIiA/IHAuZGV2aWNlU2NhbGVGYWN0b3IgOiBwcmVzZXQuZGV2aWNlU2NhbGVGYWN0b3I7CgogIGF3YWl0IGRlYnVnZ2VyQXR0YWNoRm9yVG9vbCh0YWJJZCk7CiAgdHJ5IHsKICAgIGF3YWl0IGRlYnVnZ2VyU2VuZCh0YWJJZCwgIkVtdWxhdGlvbi5zZXREZXZpY2VNZXRyaWNzT3ZlcnJpZGUiLCB7CiAgICAgIHdpZHRoOiBNYXRoLnJvdW5kKHdpZHRoKSwKICAgICAgaGVpZ2h0OiBNYXRoLnJvdW5kKGhlaWdodCksCiAgICAgIGRldmljZVNjYWxlRmFjdG9yLAogICAgICBtb2JpbGU6IHByZXNldC5tb2JpbGUsCiAgICAgIGZpdFdpbmRvdzogZmFsc2UsCiAgICAgIHNjYWxlOiAxLAogICAgfSk7CiAgICBjb25zdCB1YSA9IHR5cGVvZiBwLnVzZXJBZ2VudCA9PT0gInN0cmluZyIgJiYgcC51c2VyQWdlbnQudHJpbSgpID8gcC51c2VyQWdlbnQudHJpbSgpIDogdW5kZWZpbmVkOwogICAgaWYgKHVhKSB7CiAgICAgIGF3YWl0IGRlYnVnZ2VyU2VuZCh0YWJJZCwgIk5ldHdvcmsuc2V0VXNlckFnZW50T3ZlcnJpZGUiLCB7IHVzZXJBZ2VudDogdWEgfSk7CiAgICB9CiAgICByZXR1cm4gd2l0aFRhYk1ldGEodGFiSWQsIHsgc3VjY2VzczogdHJ1ZSB9KTsKICB9IGZpbmFsbHkgewogICAgYXdhaXQgZGVidWdnZXJEZXRhY2hGb3JUb29sKHRhYklkKTsKICB9Cn0KCi8qKiBAcGFyYW0ge3Vua25vd259IHBheWxvYWQgKi8KYXN5bmMgZnVuY3Rpb24gaGFuZGxlRXZhbHVhdGVKcyhwYXlsb2FkKSB7CiAgY29uc3QgcCA9IGFzUGF5bG9hZChwYXlsb2FkKTsKICBjb25zdCBjb2RlID0gdHlwZW9mIHAuY29kZSA9PT0gInN0cmluZyIgPyBwLmNvZGUgOiAiIjsKICBpZiAoIWNvZGUpIHRocm93IG5ldyBFcnJvcigiZXZhbHVhdGVfanMgcmVxdWlyZXMgY29kZSIpOwogIGNvbnN0IHRhYklkID0gYXdhaXQgcmVzb2x2ZVRhYklkKHR5cGVvZiBwLnRhYklkID09PSAibnVtYmVyIiA/IHAudGFiSWQgOiB1bmRlZmluZWQpOwogIGNvbnN0IHJlcXVlc3RJZCA9CiAgICB0eXBlb2YgcC5yZXF1ZXN0SWQgPT09ICJzdHJpbmciID8gcC5yZXF1ZXN0SWQgOiBgYmctJHtEYXRlLm5vdygpfS0ke01hdGgucmFuZG9tKCkudG9TdHJpbmcoMTYpLnNsaWNlKDIpfWA7CiAgY29uc3QgdGltZW91dE1zID0gdHlwZW9mIHAudGltZW91dE1zID09PSAibnVtYmVyIiA/IHAudGltZW91dE1zIDogMzAwMDA7CiAgY29uc3QgcmVzID0gYXdhaXQgY2hyb21lLnRhYnMuc2VuZE1lc3NhZ2UodGFiSWQsIHsKICAgIHR5cGU6ICJQT0tFX0VWQUwiLAogICAgY29kZSwKICAgIHJlcXVlc3RJZCwKICAgIHRpbWVvdXRNcywKICB9KS5jYXRjaCgoZSkgPT4gewogICAgdGhyb3cgbmV3IEVycm9yKGBldmFsdWF0ZV9qcyByZWxheSBmYWlsZWQ6ICR7U3RyaW5nKGUpfWApOwogIH0pOwogIHJldHVybiB3aXRoVGFiTWV0YSh0YWJJZCwgcmVzKTsKfQoKLyoqCiAqIEBwYXJhbSB7bnVtYmVyfSB0YWJJZAogKiBAcGFyYW0ge3N0cmluZ30gcG9rZVR5cGUKICogQHBhcmFtIHtSZWNvcmQ8c3RyaW5nLCB1bmtub3duPn0gZGF0YQogKi8KYXN5bmMgZnVuY3Rpb24gc2VuZFBlcmNlcHRpb25Ub1RhYih0YWJJZCwgcG9rZVR5cGUsIGRhdGEpIHsKICBjb25zdCByZXMgPSBhd2FpdCBjaHJvbWUudGFicy5zZW5kTWVzc2FnZSh0YWJJZCwgeyAuLi5kYXRhLCB0eXBlOiBwb2tlVHlwZSB9KS5jYXRjaCgoZSkgPT4gewogICAgdGhyb3cgbmV3IEVycm9yKGBQZXJjZXB0aW9uIHJlbGF5IGZhaWxlZCAoJHtwb2tlVHlwZX0pOiAke1N0cmluZyhlKX1gKTsKICB9KTsKICBpZiAocmVzICYmIHR5cGVvZiByZXMgPT09ICJvYmplY3QiICYmICJlcnJvciIgaW4gcmVzICYmIHR5cGVvZiByZXMuZXJyb3IgPT09ICJzdHJpbmciKSB7CiAgICB0aHJvdyBuZXcgRXJyb3IocmVzLmVycm9yKTsKICB9CiAgcmV0dXJuIHJlczsKfQoKLyoqIEBwYXJhbSB7dW5rbm93bn0gcGF5bG9hZCAqLwphc3luYyBmdW5jdGlvbiBoYW5kbGVHZXREb21TbmFwc2hvdChwYXlsb2FkKSB7CiAgY29uc3QgcCA9IGFzUGF5bG9hZChwYXlsb2FkKTsKICBjb25zdCB0YWJJZCA9IGF3YWl0IHJlc29sdmVUYWJJZCh0eXBlb2YgcC50YWJJZCA9PT0gIm51bWJlciIgPyBwLnRhYklkIDogdW5kZWZpbmVkKTsKICBjb25zdCByZXMgPSBhd2FpdCBzZW5kUGVyY2VwdGlvblRvVGFiKHRhYklkLCAiUE9LRV9HRVRfRE9NX1NOQVBTSE9UIiwgewogICAgaW5jbHVkZUhpZGRlbjogcC5pbmNsdWRlSGlkZGVuID09PSB0cnVlLAogICAgbWF4RGVwdGg6IHR5cGVvZiBwLm1heERlcHRoID09PSAibnVtYmVyIiA/IHAubWF4RGVwdGggOiB1bmRlZmluZWQsCiAgfSk7CiAgcmV0dXJuIHdpdGhUYWJNZXRhKHRhYklkLCByZXMpOwp9CgovKiogQHBhcmFtIHt1bmtub3dufSBwYXlsb2FkICovCmFzeW5jIGZ1bmN0aW9uIGhhbmRsZUdldEFjY2Vzc2liaWxpdHlUcmVlKHBheWxvYWQpIHsKICBjb25zdCBwID0gYXNQYXlsb2FkKHBheWxvYWQpOwogIGNvbnN0IHRhYklkID0gYXdhaXQgcmVzb2x2ZVRhYklkKHR5cGVvZiBwLnRhYklkID09PSAibnVtYmVyIiA/IHAudGFiSWQgOiB1bmRlZmluZWQpOwogIGNvbnN0IHJlcyA9IGF3YWl0IHNlbmRQZXJjZXB0aW9uVG9UYWIodGFiSWQsICJQT0tFX0dFVF9BMTFZX1RSRUUiLCB7CiAgICBpbnRlcmFjdGl2ZU9ubHk6IHAuaW50ZXJhY3RpdmVPbmx5ID09PSB0cnVlLAogIH0pOwogIHJldHVybiB3aXRoVGFiTWV0YSh0YWJJZCwgcmVzKTsKfQoKLyoqIEBwYXJhbSB7dW5rbm93bn0gcGF5bG9hZCAqLwphc3luYyBmdW5jdGlvbiBoYW5kbGVGaW5kRWxlbWVudChwYXlsb2FkKSB7CiAgY29uc3QgcCA9IGFzUGF5bG9hZChwYXlsb2FkKTsKICBjb25zdCB0YWJJZCA9IGF3YWl0IHJlc29sdmVUYWJJZCh0eXBlb2YgcC50YWJJZCA9PT0gIm51bWJlciIgPyBwLnRhYklkIDogdW5kZWZpbmVkKTsKICBjb25zdCBxdWVyeSA9IHR5cGVvZiBwLnF1ZXJ5ID09PSAic3RyaW5nIiA/IHAucXVlcnkgOiAiIjsKICBjb25zdCBzdHJhdGVneSA9CiAgICBwLnN0cmF0ZWd5ID09PSAiY3NzIiB8fCBwLnN0cmF0ZWd5ID09PSAidGV4dCIgfHwgcC5zdHJhdGVneSA9PT0gImFyaWEiIHx8IHAuc3RyYXRlZ3kgPT09ICJ4cGF0aCIKICAgICAgPyBwLnN0cmF0ZWd5CiAgICAgIDogImF1dG8iOwogIGNvbnN0IHJlcyA9IGF3YWl0IHNlbmRQZXJjZXB0aW9uVG9UYWIodGFiSWQsICJQT0tFX0ZJTkRfRUxFTUVOVCIsIHsgcXVlcnksIHN0cmF0ZWd5IH0pOwogIHJldHVybiB3aXRoVGFiTWV0YSh0YWJJZCwgcmVzKTsKfQoKLyoqIEBwYXJhbSB7dW5rbm93bn0gcGF5bG9hZCAqLwphc3luYyBmdW5jdGlvbiBoYW5kbGVSZWFkUGFnZShwYXlsb2FkKSB7CiAgY29uc3QgcCA9IGFzUGF5bG9hZChwYXlsb2FkKTsKICBjb25zdCB0YWJJZCA9IGF3YWl0IHJlc29sdmVUYWJJZCh0eXBlb2YgcC50YWJJZCA9PT0gIm51bWJlciIgPyBwLnRhYklkIDogdW5kZWZpbmVkKTsKICBjb25zdCBmb3JtYXQgPQogICAgcC5mb3JtYXQgPT09ICJtYXJrZG93biIgfHwgcC5mb3JtYXQgPT09ICJ0ZXh0IiB8fCBwLmZvcm1hdCA9PT0gInN0cnVjdHVyZWQiID8gcC5mb3JtYXQgOiAic3RydWN0dXJlZCI7CiAgY29uc3QgcmVzID0gYXdhaXQgc2VuZFBlcmNlcHRpb25Ub1RhYih0YWJJZCwgIlBPS0VfUkVBRF9QQUdFIiwgeyBmb3JtYXQgfSk7CiAgcmV0dXJuIHdpdGhUYWJNZXRhKHRhYklkLCByZXMpOwp9CgovKiogQHBhcmFtIHt1bmtub3dufSBwYXlsb2FkICovCmFzeW5jIGZ1bmN0aW9uIGhhbmRsZVdhaXRGb3JTZWxlY3RvcihwYXlsb2FkKSB7CiAgY29uc3QgcCA9IGFzUGF5bG9hZChwYXlsb2FkKTsKICBjb25zdCBzZWxlY3RvciA9IHR5cGVvZiBwLnNlbGVjdG9yID09PSAic3RyaW5nIiA/IHAuc2VsZWN0b3IgOiAiIjsKICBpZiAoIXNlbGVjdG9yLnRyaW0oKSkgdGhyb3cgbmV3IEVycm9yKCJ3YWl0X2Zvcl9zZWxlY3RvciByZXF1aXJlcyBzZWxlY3RvciIpOwogIGNvbnN0IHRhYklkID0gYXdhaXQgcmVzb2x2ZVRhYklkKHR5cGVvZiBwLnRhYklkID09PSAibnVtYmVyIiA/IHAudGFiSWQgOiB1bmRlZmluZWQpOwogIGNvbnN0IHRpbWVvdXQgPSB0eXBlb2YgcC50aW1lb3V0ID09PSAibnVtYmVyIiAmJiBwLnRpbWVvdXQgPiAwID8gcC50aW1lb3V0IDogMTAwMDA7CiAgY29uc3QgdmlzaWJsZSA9IHAudmlzaWJsZSA9PT0gdHJ1ZTsKICBjb25zdCByZXMgPSBhd2FpdCBjaHJvbWUudGFicwogICAgLnNlbmRNZXNzYWdlKHRhYklkLCB7IHR5cGU6ICJQT0tFX1dBSVRfRk9SX1NFTEVDVE9SIiwgc2VsZWN0b3IsIHRpbWVvdXQsIHZpc2libGUgfSkKICAgIC5jYXRjaCgoZSkgPT4gewogICAgICB0aHJvdyBuZXcgRXJyb3IoYHdhaXRfZm9yX3NlbGVjdG9yIHJlbGF5IGZhaWxlZDogJHtTdHJpbmcoZSl9YCk7CiAgICB9KTsKICByZXR1cm4gd2l0aFRhYk1ldGEodGFiSWQsIHJlcyk7Cn0KCi8qKiBAcGFyYW0ge3Vua25vd259IHBheWxvYWQgKi8KYXN5bmMgZnVuY3Rpb24gaGFuZGxlRXhlY3V0ZVNjcmlwdChwYXlsb2FkKSB7CiAgY29uc3QgcCA9IGFzUGF5bG9hZChwYXlsb2FkKTsKICBjb25zdCBzY3JpcHQgPSB0eXBlb2YgcC5zY3JpcHQgPT09ICJzdHJpbmciID8gcC5zY3JpcHQgOiAiIjsKICBpZiAoIXNjcmlwdC50cmltKCkpIHRocm93IG5ldyBFcnJvcigiZXhlY3V0ZV9zY3JpcHQgcmVxdWlyZXMgc2NyaXB0Iik7CiAgY29uc3QgdGFiSWQgPSBhd2FpdCByZXNvbHZlVGFiSWQodHlwZW9mIHAudGFiSWQgPT09ICJudW1iZXIiID8gcC50YWJJZCA6IHVuZGVmaW5lZCk7CiAgY29uc3QgYXJncyA9IEFycmF5LmlzQXJyYXkocC5hcmdzKSA/IHAuYXJncyA6IFtdOwoKICBjb25zdCByZXN1bHRzID0gYXdhaXQgY2hyb21lLnNjcmlwdGluZy5leGVjdXRlU2NyaXB0KHsKICAgIHRhcmdldDogeyB0YWJJZCwgYWxsRnJhbWVzOiBmYWxzZSB9LAogICAgd29ybGQ6ICJNQUlOIiwKICAgIGluamVjdEltbWVkaWF0ZWx5OiB0cnVlLAogICAgZnVuYzogYXN5bmMgKHNjcmlwdFNvdXJjZSwgY2FsbEFyZ3MpID0+IHsKICAgICAgY29uc3Qgc2VlbiA9IG5ldyBXZWFrU2V0KCk7CiAgICAgIC8qKgogICAgICAgKiBAcGFyYW0ge3N0cmluZ30gX2sKICAgICAgICogQHBhcmFtIHt1bmtub3dufSB2YWwKICAgICAgICovCiAgICAgIGZ1bmN0aW9uIHJlcGxhY2VyKF9rLCB2YWwpIHsKICAgICAgICBpZiAodHlwZW9mIHZhbCA9PT0gImJpZ2ludCIpIHJldHVybiB2YWwudG9TdHJpbmcoKTsKICAgICAgICBpZiAodHlwZW9mIHZhbCA9PT0gIm9iamVjdCIgJiYgdmFsICE9PSBudWxsKSB7CiAgICAgICAgICBpZiAoc2Vlbi5oYXMoLyoqIEB0eXBlIHtvYmplY3R9ICovICh2YWwpKSkgcmV0dXJuICJbQ2lyY3VsYXJdIjsKICAgICAgICAgIHNlZW4uYWRkKC8qKiBAdHlwZSB7b2JqZWN0fSAqLyAodmFsKSk7CiAgICAgICAgfQogICAgICAgIHJldHVybiB2YWw7CiAgICAgIH0KICAgICAgdHJ5IHsKICAgICAgICBjb25zdCBBc3luY0Z1bmN0aW9uID0gT2JqZWN0LmdldFByb3RvdHlwZU9mKGFzeW5jIGZ1bmN0aW9uICgpIHt9KS5jb25zdHJ1Y3RvcjsKICAgICAgICBjb25zdCBmbiA9IG5ldyBBc3luY0Z1bmN0aW9uKCJhcmdzIiwgYHJldHVybiAoYXN5bmMgKCkgPT4ge1xuJHtzY3JpcHRTb3VyY2V9XG59KSgpO2ApOwogICAgICAgIGNvbnN0IHJhdyA9IGF3YWl0IGZuKGNhbGxBcmdzID8/IFtdKTsKICAgICAgICB0cnkgewogICAgICAgICAgcmV0dXJuIHsgcmVzdWx0OiBKU09OLnBhcnNlKEpTT04uc3RyaW5naWZ5KHJhdywgcmVwbGFjZXIpKSB9OwogICAgICAgIH0gY2F0Y2ggKHNlckVycikgewogICAgICAgICAgcmV0dXJuIHsKICAgICAgICAgICAgcmVzdWx0OiBTdHJpbmcocmF3KSwKICAgICAgICAgICAgZXJyb3I6IGBzZXJpYWxpemF0aW9uOiAke3NlckVyciBpbnN0YW5jZW9mIEVycm9yID8gc2VyRXJyLm1lc3NhZ2UgOiBTdHJpbmcoc2VyRXJyKX1gLAogICAgICAgICAgfTsKICAgICAgICB9CiAgICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgICByZXR1cm4geyBlcnJvcjogU3RyaW5nKGUpIH07CiAgICAgIH0KICAgIH0sCiAgICBhcmdzOiBbc2NyaXB0LCBhcmdzXSwKICB9KTsKCiAgY29uc3QgZnIgPSAvKiogQHR5cGUge3sgcmVzdWx0PzogdW5rbm93bjsgZXJyb3I/OiBzdHJpbmcgfSB8IHVuZGVmaW5lZH0gKi8gKHJlc3VsdHNbMF0/LnJlc3VsdCk7CiAgaWYgKCFmcikgcmV0dXJuIHdpdGhUYWJNZXRhKHRhYklkLCB7IHJlc3VsdDogbnVsbCwgZXJyb3I6ICJObyBmcmFtZSByZXN1bHQiIH0pOwogIGlmICh0eXBlb2YgZnIuZXJyb3IgPT09ICJzdHJpbmciICYmIGZyLmVycm9yICYmIGZyLnJlc3VsdCA9PT0gdW5kZWZpbmVkKSB7CiAgICByZXR1cm4gd2l0aFRhYk1ldGEodGFiSWQsIHsgcmVzdWx0OiB1bmRlZmluZWQsIGVycm9yOiBmci5lcnJvciB9KTsKICB9CiAgcmV0dXJuIHdpdGhUYWJNZXRhKHRhYklkLCB7CiAgICByZXN1bHQ6IGZyLnJlc3VsdCwKICAgIGVycm9yOiB0eXBlb2YgZnIuZXJyb3IgPT09ICJzdHJpbmciID8gZnIuZXJyb3IgOiB1bmRlZmluZWQsCiAgfSk7Cn0KCi8qKiBAcGFyYW0ge3Vua25vd259IHBheWxvYWQgKi8KYXN5bmMgZnVuY3Rpb24gaGFuZGxlR2V0Q29uc29sZUxvZ3MocGF5bG9hZCkgewogIGNvbnN0IHAgPSBhc1BheWxvYWQocGF5bG9hZCk7CiAgY29uc3QgdGFiSWQgPSBhd2FpdCByZXNvbHZlVGFiSWQodHlwZW9mIHAudGFiSWQgPT09ICJudW1iZXIiID8gcC50YWJJZCA6IHVuZGVmaW5lZCk7CiAgY29uc3QgbGV2ZWwgPQogICAgcC5sZXZlbCA9PT0gImVycm9yIiB8fCBwLmxldmVsID09PSAid2FybiIgfHwgcC5sZXZlbCA9PT0gImluZm8iIHx8IHAubGV2ZWwgPT09ICJsb2ciIHx8IHAubGV2ZWwgPT09ICJhbGwiCiAgICAgID8gcC5sZXZlbAogICAgICA6ICJhbGwiOwogIGNvbnN0IGxpbWl0ID0gdHlwZW9mIHAubGltaXQgPT09ICJudW1iZXIiID8gTWF0aC5taW4oNTAwLCBNYXRoLm1heCgxLCBwLmxpbWl0KSkgOiAxMDA7CiAgY29uc3QgcmVzID0gYXdhaXQgY2hyb21lLnRhYnMKICAgIC5zZW5kTWVzc2FnZSh0YWJJZCwgeyB0eXBlOiAiUE9LRV9HRVRfQ09OU09MRV9MT0dTIiwgbGV2ZWwsIGxpbWl0IH0pCiAgICAuY2F0Y2goKGUpID0+IHsKICAgICAgdGhyb3cgbmV3IEVycm9yKGBnZXRfY29uc29sZV9sb2dzIHJlbGF5IGZhaWxlZDogJHtTdHJpbmcoZSl9YCk7CiAgICB9KTsKICBjb25zdCBsb2dzID0gcmVzICYmIHR5cGVvZiByZXMgPT09ICJvYmplY3QiICYmICJsb2dzIiBpbiByZXMgPyAvKiogQHR5cGUge3sgbG9nczogdW5rbm93biB9fSAqLyAocmVzKS5sb2dzIDogW107CiAgY29uc3QgY291bnQgPSByZXMgJiYgdHlwZW9mIHJlcyA9PT0gIm9iamVjdCIgJiYgImNvdW50IiBpbiByZXMgPyBOdW1iZXIoLyoqIEB0eXBlIHt7IGNvdW50PzogbnVtYmVyIH19ICovIChyZXMpLmNvdW50KSA6IDA7CiAgcmV0dXJuIHsgbG9ncywgY291bnQsIHRhYklkIH07Cn0KCi8qKiBAcGFyYW0ge3Vua25vd259IHBheWxvYWQgKi8KYXN5bmMgZnVuY3Rpb24gaGFuZGxlQ2xlYXJDb25zb2xlTG9ncyhwYXlsb2FkKSB7CiAgY29uc3QgcCA9IGFzUGF5bG9hZChwYXlsb2FkKTsKICBjb25zdCB0YWJJZCA9IGF3YWl0IHJlc29sdmVUYWJJZCh0eXBlb2YgcC50YWJJZCA9PT0gIm51bWJlciIgPyBwLnRhYklkIDogdW5kZWZpbmVkKTsKICBhd2FpdCBjaHJvbWUudGFicy5zZW5kTWVzc2FnZSh0YWJJZCwgeyB0eXBlOiAiUE9LRV9DTEVBUl9DT05TT0xFX0xPR1MiIH0pLmNhdGNoKChlKSA9PiB7CiAgICB0aHJvdyBuZXcgRXJyb3IoYGNsZWFyX2NvbnNvbGVfbG9ncyByZWxheSBmYWlsZWQ6ICR7U3RyaW5nKGUpfWApOwogIH0pOwogIHJldHVybiB7IGNsZWFyZWQ6IHRydWUsIHRhYklkIH07Cn0KCi8qKiBAcGFyYW0ge3Vua25vd259IHBheWxvYWQgKi8KYXN5bmMgZnVuY3Rpb24gaGFuZGxlU3RhcnROZXR3b3JrQ2FwdHVyZShwYXlsb2FkKSB7CiAgY29uc3QgcCA9IGFzUGF5bG9hZChwYXlsb2FkKTsKICBjb25zdCB0YWJJZCA9IGF3YWl0IHJlc29sdmVUYWJJZCh0eXBlb2YgcC50YWJJZCA9PT0gIm51bWJlciIgPyBwLnRhYklkIDogdW5kZWZpbmVkKTsKICBuZXR3b3JrU3RhdGVCeVRhYi5kZWxldGUodGFiSWQpOwogIGlmICghbmV0d29ya0NhcHR1cmVUYWJzLmhhcyh0YWJJZCkpIHsKICAgIGF3YWl0IGRlYnVnZ2VyQXR0YWNoKHRhYklkKTsKICAgIG5ldHdvcmtDYXB0dXJlVGFicy5hZGQodGFiSWQpOwogIH0KICBhd2FpdCBkZWJ1Z2dlclNlbmQodGFiSWQsICJOZXR3b3JrLmVuYWJsZSIsIHt9KTsKICByZXR1cm4geyBzdWNjZXNzOiB0cnVlLCB0YWJJZCwgY2FwdHVyaW5nOiB0cnVlIH07Cn0KCi8qKiBAcGFyYW0ge3Vua25vd259IHBheWxvYWQgKi8KYXN5bmMgZnVuY3Rpb24gaGFuZGxlU3RvcE5ldHdvcmtDYXB0dXJlKHBheWxvYWQpIHsKICBjb25zdCBwID0gYXNQYXlsb2FkKHBheWxvYWQpOwogIGNvbnN0IHRhYklkID0gYXdhaXQgcmVzb2x2ZVRhYklkKHR5cGVvZiBwLnRhYklkID09PSAibnVtYmVyIiA/IHAudGFiSWQgOiB1bmRlZmluZWQpOwogIGlmICghbmV0d29ya0NhcHR1cmVUYWJzLmhhcyh0YWJJZCkpIHsKICAgIHJldHVybiB7IHN1Y2Nlc3M6IHRydWUsIHRhYklkLCBjYXB0dXJpbmc6IGZhbHNlIH07CiAgfQogIHRyeSB7CiAgICBhd2FpdCBkZWJ1Z2dlclNlbmQodGFiSWQsICJOZXR3b3JrLmRpc2FibGUiLCB7fSk7CiAgfSBjYXRjaCB7CiAgICAvKiBpZ25vcmUgKi8KICB9CiAgbmV0d29ya0NhcHR1cmVUYWJzLmRlbGV0ZSh0YWJJZCk7CiAgYXdhaXQgZGVidWdnZXJEZXRhY2godGFiSWQpOwogIHJldHVybiB7IHN1Y2Nlc3M6IHRydWUsIHRhYklkLCBjYXB0dXJpbmc6IGZhbHNlIH07Cn0KCi8qKiBAcGFyYW0ge3Vua25vd259IHBheWxvYWQgKi8KYXN5bmMgZnVuY3Rpb24gaGFuZGxlR2V0TmV0d29ya0xvZ3MocGF5bG9hZCkgewogIGNvbnN0IHAgPSBhc1BheWxvYWQocGF5bG9hZCk7CiAgY29uc3QgdGFiSWQgPSBhd2FpdCByZXNvbHZlVGFiSWQodHlwZW9mIHAudGFiSWQgPT09ICJudW1iZXIiID8gcC50YWJJZCA6IHVuZGVmaW5lZCk7CiAgY29uc3QgZmlsdGVyID0gdHlwZW9mIHAuZmlsdGVyID09PSAic3RyaW5nIiA/IHAuZmlsdGVyIDogIiI7CiAgY29uc3QgbGltaXQgPSB0eXBlb2YgcC5saW1pdCA9PT0gIm51bWJlciIgPyBNYXRoLm1pbigyMDAsIE1hdGgubWF4KDEsIHAubGltaXQpKSA6IDUwOwogIGNvbnN0IGluY2x1ZGVCb2R5ID0gcC5pbmNsdWRlQm9keSA9PT0gdHJ1ZTsKCiAgY29uc3Qgc3RhdGUgPSBuZXR3b3JrU3RhdGVCeVRhYi5nZXQodGFiSWQpOwogIGlmICghc3RhdGUpIHsKICAgIHJldHVybiB7IHJlcXVlc3RzOiBbXSwgY291bnQ6IDAgfTsKICB9CgogIC8qKiBAdHlwZSB7UmVjb3JkPHN0cmluZywgdW5rbm93bj5bXX0gKi8KICBjb25zdCByb3dzID0gW107CiAgZm9yIChjb25zdCByaWQgb2Ygc3RhdGUub3JkZXIpIHsKICAgIGNvbnN0IHJvdyA9IHN0YXRlLmJ5SWQuZ2V0KHJpZCk7CiAgICBpZiAocm93KSByb3dzLnB1c2gocm93KTsKICB9CiAgbGV0IGZpbHRlcmVkID0gZmlsdGVyID8gcm93cy5maWx0ZXIoKHIpID0+IFN0cmluZyhyLnVybCA/PyAiIikuaW5jbHVkZXMoZmlsdGVyKSkgOiBbLi4ucm93c107CiAgZmlsdGVyZWQgPSBmaWx0ZXJlZC5zbGljZSgtbGltaXQpOwoKICBjb25zdCBuZWVkVGVtcEF0dGFjaCA9IGluY2x1ZGVCb2R5ICYmICFuZXR3b3JrQ2FwdHVyZVRhYnMuaGFzKHRhYklkKTsKICBpZiAobmVlZFRlbXBBdHRhY2gpIHsKICAgIGF3YWl0IGRlYnVnZ2VyQXR0YWNoKHRhYklkKTsKICB9CiAgdHJ5IHsKICAgIC8qKiBAdHlwZSB7UmVjb3JkPHN0cmluZywgdW5rbm93bj5bXX0gKi8KICAgIGNvbnN0IG91dCA9IFtdOwogICAgZm9yIChjb25zdCBlIG9mIGZpbHRlcmVkKSB7CiAgICAgIGNvbnN0IGNvcHkgPSB7IC4uLmUgfTsKICAgICAgaWYgKGluY2x1ZGVCb2R5ICYmIGUubG9hZGVkID09PSB0cnVlICYmIHR5cGVvZiBlLnJlcXVlc3RJZCA9PT0gInN0cmluZyIpIHsKICAgICAgICB0cnkgewogICAgICAgICAgY29uc3QgYm9keVJlcyA9IC8qKiBAdHlwZSB7eyBib2R5Pzogc3RyaW5nOyBiYXNlNjRFbmNvZGVkPzogYm9vbGVhbiB9fSAqLyAoCiAgICAgICAgICAgIGF3YWl0IGRlYnVnZ2VyU2VuZFdpdGhSZXN1bHQodGFiSWQsICJOZXR3b3JrLmdldFJlc3BvbnNlQm9keSIsIHsgcmVxdWVzdElkOiBlLnJlcXVlc3RJZCB9KQogICAgICAgICAgKTsKICAgICAgICAgIGNvcHkuYm9keSA9IGJvZHlSZXMuYm9keTsKICAgICAgICAgIGNvcHkuYm9keUJhc2U2NEVuY29kZWQgPSBib2R5UmVzLmJhc2U2NEVuY29kZWQgPT09IHRydWU7CiAgICAgICAgfSBjYXRjaCB7CiAgICAgICAgICBjb3B5LmJvZHlGZXRjaEVycm9yID0gIk5ldHdvcmsuZ2V0UmVzcG9uc2VCb2R5IGZhaWxlZCI7CiAgICAgICAgfQogICAgICB9CiAgICAgIG91dC5wdXNoKGNvcHkpOwogICAgfQogICAgcmV0dXJuIHsgcmVxdWVzdHM6IG91dCwgY291bnQ6IG91dC5sZW5ndGggfTsKICB9IGZpbmFsbHkgewogICAgaWYgKG5lZWRUZW1wQXR0YWNoKSB7CiAgICAgIGF3YWl0IGRlYnVnZ2VyRGV0YWNoKHRhYklkKTsKICAgIH0KICB9Cn0KCi8qKiBAcGFyYW0ge3Vua25vd259IHBheWxvYWQgKi8KYXN5bmMgZnVuY3Rpb24gaGFuZGxlQ2xlYXJOZXR3b3JrTG9ncyhwYXlsb2FkKSB7CiAgY29uc3QgcCA9IGFzUGF5bG9hZChwYXlsb2FkKTsKICBjb25zdCB0YWJJZCA9IGF3YWl0IHJlc29sdmVUYWJJZCh0eXBlb2YgcC50YWJJZCA9PT0gIm51bWJlciIgPyBwLnRhYklkIDogdW5kZWZpbmVkKTsKICBuZXR3b3JrU3RhdGVCeVRhYi5kZWxldGUodGFiSWQpOwogIHJldHVybiB7IGNsZWFyZWQ6IHRydWUsIHRhYklkIH07Cn0KCmNvbnN0IFBFUlNJU1RFTlRfTE9BREVSX0lEID0gInBva2UtYnJvd3Nlci1wZXJzaXN0ZW50LWxvYWRlciI7CgovKioKICogQHBhcmFtIHtjaHJvbWUuY29va2llcy5Db29raWV9IGMKICovCmZ1bmN0aW9uIGNvb2tpZVJlbW92ZVVybChjKSB7CiAgY29uc3QgZG9tID0gYy5kb21haW4uc3RhcnRzV2l0aCgiLiIpID8gYy5kb21haW4uc2xpY2UoMSkgOiBjLmRvbWFpbjsKICBjb25zdCBzY2hlbWUgPSBjLnNlY3VyZSA/ICJodHRwcyIgOiAiaHR0cCI7CiAgY29uc3QgcGF0aCA9IGMucGF0aCAmJiBjLnBhdGgubGVuZ3RoID8gYy5wYXRoIDogIi8iOwogIHJldHVybiBgJHtzY2hlbWV9Oi8vJHtkb219JHtwYXRofWA7Cn0KCi8qKgogKiBAcGFyYW0ge2Nocm9tZS5jb29raWVzLkNvb2tpZX0gYwogKi8KZnVuY3Rpb24gc2VyaWFsaXplQ29va2llKGMpIHsKICByZXR1cm4gewogICAgbmFtZTogYy5uYW1lLAogICAgdmFsdWU6IGMudmFsdWUsCiAgICBkb21haW46IGMuZG9tYWluLAogICAgcGF0aDogYy5wYXRoLAogICAgc2VjdXJlOiBjLnNlY3VyZSwKICAgIGh0dHBPbmx5OiBjLmh0dHBPbmx5LAogICAgc2FtZVNpdGU6IGMuc2FtZVNpdGUsCiAgICBleHBpcmF0aW9uRGF0ZTogYy5leHBpcmF0aW9uRGF0ZSwKICAgIHNlc3Npb246IGMuc2Vzc2lvbiwKICB9Owp9Cgphc3luYyBmdW5jdGlvbiBlbnN1cmVQZXJzaXN0ZW50TG9hZGVyUmVnaXN0ZXJlZCgpIHsKICBjb25zdCBleGlzdGluZyA9IGF3YWl0IGNocm9tZS5zY3JpcHRpbmcuZ2V0UmVnaXN0ZXJlZENvbnRlbnRTY3JpcHRzKHsgaWRzOiBbUEVSU0lTVEVOVF9MT0FERVJfSURdIH0pOwogIGlmIChBcnJheS5pc0FycmF5KGV4aXN0aW5nKSAmJiBleGlzdGluZy5sZW5ndGggPiAwKSByZXR1cm47CiAgYXdhaXQgY2hyb21lLnNjcmlwdGluZy5yZWdpc3RlckNvbnRlbnRTY3JpcHRzKFsKICAgIHsKICAgICAgaWQ6IFBFUlNJU1RFTlRfTE9BREVSX0lELAogICAgICBtYXRjaGVzOiBbIjxhbGxfdXJscz4iXSwKICAgICAganM6IFsicGVyc2lzdGVudC1sb2FkZXIuanMiXSwKICAgICAgcnVuQXQ6ICJkb2N1bWVudF9zdGFydCIsCiAgICB9LAogIF0pOwp9CgovKioKICogQHBhcmFtIHtudW1iZXJ9IHRhYklkCiAqLwphc3luYyBmdW5jdGlvbiB0YWJIdHRwVXJsKHRhYklkKSB7CiAgY29uc3QgdGFiID0gYXdhaXQgY2hyb21lLnRhYnMuZ2V0KHRhYklkKTsKICBjb25zdCB1ID0gdGFiLnVybCA/PyAiIjsKICBpZiAoIXUuc3RhcnRzV2l0aCgiaHR0cDovLyIpICYmICF1LnN0YXJ0c1dpdGgoImh0dHBzOi8vIikpIHsKICAgIHRocm93IG5ldyBFcnJvcigiVGFiIG11c3QgaGF2ZSBhbiBodHRwKHMpIFVSTCBmb3IgdGhpcyBvcGVyYXRpb24iKTsKICB9CiAgcmV0dXJuIHU7Cn0KCi8qKiBAcGFyYW0ge3Vua25vd259IHBheWxvYWQgKi8KYXN5bmMgZnVuY3Rpb24gaGFuZGxlU2NyaXB0SW5qZWN0KHBheWxvYWQpIHsKICBjb25zdCBwID0gYXNQYXlsb2FkKHBheWxvYWQpOwogIGNvbnN0IHNjcmlwdCA9IHR5cGVvZiBwLnNjcmlwdCA9PT0gInN0cmluZyIgPyBwLnNjcmlwdCA6ICIiOwogIGlmICghc2NyaXB0LnRyaW0oKSkgdGhyb3cgbmV3IEVycm9yKCJzY3JpcHRfaW5qZWN0IHJlcXVpcmVzIHNjcmlwdCIpOwogIGNvbnN0IHRhYklkID0gYXdhaXQgcmVzb2x2ZVRhYklkKHR5cGVvZiBwLnRhYklkID09PSAibnVtYmVyIiA/IHAudGFiSWQgOiB1bmRlZmluZWQpOwogIGF3YWl0IHRhYkh0dHBVcmwodGFiSWQpOwogIGNvbnN0IHBlcnNpc3RlbnQgPSBwLnBlcnNpc3RlbnQgPT09IHRydWU7CiAgY29uc3QgcnVuQXQgPQogICAgcC5ydW5BdCA9PT0gImRvY3VtZW50X3N0YXJ0IiB8fCBwLnJ1bkF0ID09PSAiZG9jdW1lbnRfZW5kIiB8fCBwLnJ1bkF0ID09PSAiZG9jdW1lbnRfaWRsZSIKICAgICAgPyBwLnJ1bkF0CiAgICAgIDogImRvY3VtZW50X2lkbGUiOwoKICBpZiAocGVyc2lzdGVudCkgewogICAgY29uc3QgdGFiID0gYXdhaXQgY2hyb21lLnRhYnMuZ2V0KHRhYklkKTsKICAgIGNvbnN0IHVybCA9IHRhYi51cmwgPz8gIiI7CiAgICBjb25zdCB1ID0gbmV3IFVSTCh1cmwpOwogICAgY29uc3QgbWF0Y2hQYXR0ZXJuID0gYCR7dS5vcmlnaW59LypgOwogICAgY29uc3QgaW5qZWN0aW9uSWQgPSBgcG9rZS0ke2NyeXB0by5yYW5kb21VVUlEKCl9YDsKICAgIGNvbnN0IGdvdCA9IGF3YWl0IGNocm9tZS5zdG9yYWdlLmxvY2FsLmdldCgicG9rZVBlcnNpc3RlbnRJbmplY3Rpb25zIik7CiAgICBjb25zdCBsaXN0ID0gQXJyYXkuaXNBcnJheShnb3QucG9rZVBlcnNpc3RlbnRJbmplY3Rpb25zKSA/IGdvdC5wb2tlUGVyc2lzdGVudEluamVjdGlvbnMgOiBbXTsKICAgIGxpc3QucHVzaCh7IGlkOiBpbmplY3Rpb25JZCwgbWF0Y2hQYXR0ZXJuLCBzY3JpcHQsIHJ1bkF0IH0pOwogICAgYXdhaXQgY2hyb21lLnN0b3JhZ2UubG9jYWwuc2V0KHsgcG9rZVBlcnNpc3RlbnRJbmplY3Rpb25zOiBsaXN0IH0pOwogICAgYXdhaXQgZW5zdXJlUGVyc2lzdGVudExvYWRlclJlZ2lzdGVyZWQoKTsKICAgIHJldHVybiB3aXRoVGFiTWV0YSh0YWJJZCwgeyBzdWNjZXNzOiB0cnVlLCBpbmplY3Rpb25JZCB9KTsKICB9CgogIGlmIChydW5BdCA9PT0gImRvY3VtZW50X2lkbGUiKSB7CiAgICBjb25zdCByZXMgPSBhd2FpdCBjaHJvbWUudGFicy5zZW5kTWVzc2FnZSh0YWJJZCwgeyB0eXBlOiAiUE9LRV9TQ1JJUFRfSU5KRUNUIiwgc2NyaXB0IH0pLmNhdGNoKChlKSA9PiB7CiAgICAgIHRocm93IG5ldyBFcnJvcihgc2NyaXB0X2luamVjdCByZWxheSBmYWlsZWQ6ICR7U3RyaW5nKGUpfWApOwogICAgfSk7CiAgICByZXR1cm4gd2l0aFRhYk1ldGEodGFiSWQsIHsgc3VjY2VzczogQm9vbGVhbihyZXMgJiYgcmVzLnN1Y2Nlc3MgPT09IHRydWUpIH0pOwogIH0KCiAgYXdhaXQgY2hyb21lLnNjcmlwdGluZy5leGVjdXRlU2NyaXB0KHsKICAgIHRhcmdldDogeyB0YWJJZCwgYWxsRnJhbWVzOiBmYWxzZSB9LAogICAgd29ybGQ6ICJNQUlOIiwKICAgIGluamVjdEltbWVkaWF0ZWx5OiBydW5BdCA9PT0gImRvY3VtZW50X3N0YXJ0IiwKICAgIGZ1bmM6IChjb2RlKSA9PiB7CiAgICAgIGNvbnN0IHMgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJzY3JpcHQiKTsKICAgICAgcy50ZXh0Q29udGVudCA9IGNvZGU7CiAgICAgIGNvbnN0IHIgPSBkb2N1bWVudC5kb2N1bWVudEVsZW1lbnQgfHwgZG9jdW1lbnQuaGVhZCB8fCBkb2N1bWVudC5ib2R5OwogICAgICBpZiAocikgewogICAgICAgIHIuYXBwZW5kQ2hpbGQocyk7CiAgICAgICAgcy5yZW1vdmUoKTsKICAgICAgfQogICAgfSwKICAgIGFyZ3M6IFtzY3JpcHRdLAogIH0pOwogIHJldHVybiB3aXRoVGFiTWV0YSh0YWJJZCwgeyBzdWNjZXNzOiB0cnVlIH0pOwp9CgovKiogQHBhcmFtIHt1bmtub3dufSBwYXlsb2FkICovCmFzeW5jIGZ1bmN0aW9uIGhhbmRsZUNvb2tpZU1hbmFnZXIocGF5bG9hZCkgewogIGNvbnN0IHAgPSBhc1BheWxvYWQocGF5bG9hZCk7CiAgY29uc3QgYWN0aW9uID0KICAgIHAuYWN0aW9uID09PSAiZ2V0IiB8fCBwLmFjdGlvbiA9PT0gImdldF9hbGwiIHx8IHAuYWN0aW9uID09PSAic2V0IiB8fCBwLmFjdGlvbiA9PT0gImRlbGV0ZSIgfHwgcC5hY3Rpb24gPT09ICJkZWxldGVfYWxsIgogICAgICA/IHAuYWN0aW9uCiAgICAgIDogbnVsbDsKICBpZiAoIWFjdGlvbikgdGhyb3cgbmV3IEVycm9yKCJjb29raWVfbWFuYWdlciByZXF1aXJlcyBhY3Rpb24iKTsKCiAgY29uc3QgdGFiSWQgPQogICAgdHlwZW9mIHAudGFiSWQgPT09ICJudW1iZXIiICYmIE51bWJlci5pc0Zpbml0ZShwLnRhYklkKSA/IGF3YWl0IHJlc29sdmVUYWJJZChwLnRhYklkKSA6IHVuZGVmaW5lZDsKCiAgLyoqIEB0eXBlIHtzdHJpbmcgfCB1bmRlZmluZWR9ICovCiAgbGV0IGJhc2VVcmwgPSB0eXBlb2YgcC51cmwgPT09ICJzdHJpbmciICYmIHAudXJsLmxlbmd0aCA+IDAgPyBwLnVybCA6IHVuZGVmaW5lZDsKICBpZiAoIWJhc2VVcmwgJiYgdGFiSWQgIT0gbnVsbCkgewogICAgdHJ5IHsKICAgICAgYmFzZVVybCA9IGF3YWl0IHRhYkh0dHBVcmwodGFiSWQpOwogICAgfSBjYXRjaCB7CiAgICAgIC8qIHRhYiBtYXkgYmUgaW52YWxpZCBmb3IgaHR0cChzKTsgbGVhdmUgYmFzZVVybCB1bnNldCAqLwogICAgfQogIH0KCiAgaWYgKGFjdGlvbiA9PT0gImdldCIpIHsKICAgIGNvbnN0IG5hbWUgPSB0eXBlb2YgcC5uYW1lID09PSAic3RyaW5nIiA/IHAubmFtZSA6ICIiOwogICAgaWYgKCFuYW1lKSB0aHJvdyBuZXcgRXJyb3IoImNvb2tpZSBnZXQgcmVxdWlyZXMgbmFtZSIpOwogICAgaWYgKCFiYXNlVXJsKSB0aHJvdyBuZXcgRXJyb3IoImNvb2tpZSBnZXQgcmVxdWlyZXMgdXJsIG9yIGh0dHAocykgdGFiSWQiKTsKICAgIGNvbnN0IGMgPSBhd2FpdCBjaHJvbWUuY29va2llcy5nZXQoeyB1cmw6IGJhc2VVcmwsIG5hbWUgfSk7CiAgICByZXR1cm4geyBzdWNjZXNzOiB0cnVlLCBjb29raWU6IGMgPyBzZXJpYWxpemVDb29raWUoYykgOiB1bmRlZmluZWQgfTsKICB9CgogIGlmIChhY3Rpb24gPT09ICJnZXRfYWxsIikgewogICAgLyoqIEB0eXBlIHtjaHJvbWUuY29va2llcy5HZXRBbGxEZXRhaWxzfSAqLwogICAgY29uc3QgcSA9IHt9OwogICAgaWYgKGJhc2VVcmwpIHEudXJsID0gYmFzZVVybDsKICAgIGNvbnN0IGRvbSA9IHR5cGVvZiBwLmRvbWFpbiA9PT0gInN0cmluZyIgJiYgcC5kb21haW4ubGVuZ3RoID4gMCA/IHAuZG9tYWluIDogdW5kZWZpbmVkOwogICAgaWYgKGRvbSkgcS5kb21haW4gPSBkb207CiAgICBpZiAoIXEudXJsICYmICFxLmRvbWFpbikgdGhyb3cgbmV3IEVycm9yKCJjb29raWUgZ2V0X2FsbCByZXF1aXJlcyB1cmwvZG9tYWluIG9yIGh0dHAocykgdGFiSWQiKTsKICAgIGNvbnN0IGFsbCA9IGF3YWl0IGNocm9tZS5jb29raWVzLmdldEFsbChxKTsKICAgIGNvbnN0IGNvb2tpZXMgPSBhbGwubWFwKHNlcmlhbGl6ZUNvb2tpZSk7CiAgICByZXR1cm4geyBzdWNjZXNzOiB0cnVlLCBjb29raWU6IGNvb2tpZXMgfTsKICB9CgogIGlmIChhY3Rpb24gPT09ICJzZXQiKSB7CiAgICBjb25zdCBuYW1lID0gdHlwZW9mIHAubmFtZSA9PT0gInN0cmluZyIgPyBwLm5hbWUgOiAiIjsKICAgIGlmICghbmFtZSkgdGhyb3cgbmV3IEVycm9yKCJjb29raWUgc2V0IHJlcXVpcmVzIG5hbWUiKTsKICAgIGNvbnN0IHZhbHVlID0gdHlwZW9mIHAudmFsdWUgPT09ICJzdHJpbmciID8gcC52YWx1ZSA6ICIiOwogICAgaWYgKCFiYXNlVXJsICYmIHR5cGVvZiBwLmRvbWFpbiAhPT0gInN0cmluZyIpIHsKICAgICAgdGhyb3cgbmV3IEVycm9yKCJjb29raWUgc2V0IHJlcXVpcmVzIHVybCBvciB0YWIgd2l0aCBodHRwKHMpIFVSTCwgb3IgZG9tYWluIik7CiAgICB9CiAgICAvKiogQHR5cGUge2Nocm9tZS5jb29raWVzLlNldERldGFpbHN9ICovCiAgICBjb25zdCBkZXRhaWxzID0geyBuYW1lLCB2YWx1ZSB9OwogICAgaWYgKGJhc2VVcmwpIGRldGFpbHMudXJsID0gYmFzZVVybDsKICAgIGlmICh0eXBlb2YgcC5kb21haW4gPT09ICJzdHJpbmciKSBkZXRhaWxzLmRvbWFpbiA9IHAuZG9tYWluOwogICAgaWYgKHR5cGVvZiBwLnBhdGggPT09ICJzdHJpbmciKSBkZXRhaWxzLnBhdGggPSBwLnBhdGg7CiAgICBpZiAocC5zZWN1cmUgPT09IHRydWUpIGRldGFpbHMuc2VjdXJlID0gdHJ1ZTsKICAgIGlmIChwLmh0dHBPbmx5ID09PSB0cnVlKSBkZXRhaWxzLmh0dHBPbmx5ID0gdHJ1ZTsKICAgIGlmICh0eXBlb2YgcC5leHBpcmF0aW9uRGF0ZSA9PT0gIm51bWJlciIpIGRldGFpbHMuZXhwaXJhdGlvbkRhdGUgPSBwLmV4cGlyYXRpb25EYXRlOwogICAgY29uc3QgYyA9IGF3YWl0IGNocm9tZS5jb29raWVzLnNldChkZXRhaWxzKTsKICAgIGlmICghYykgcmV0dXJuIHsgc3VjY2VzczogZmFsc2UsIGNvb2tpZTogdW5kZWZpbmVkIH07CiAgICByZXR1cm4geyBzdWNjZXNzOiB0cnVlLCBjb29raWU6IHNlcmlhbGl6ZUNvb2tpZShjKSB9OwogIH0KCiAgaWYgKGFjdGlvbiA9PT0gImRlbGV0ZSIpIHsKICAgIGNvbnN0IG5hbWUgPSB0eXBlb2YgcC5uYW1lID09PSAic3RyaW5nIiA/IHAubmFtZSA6ICIiOwogICAgaWYgKCFuYW1lKSB0aHJvdyBuZXcgRXJyb3IoImNvb2tpZSBkZWxldGUgcmVxdWlyZXMgbmFtZSIpOwogICAgaWYgKCFiYXNlVXJsKSB0aHJvdyBuZXcgRXJyb3IoImNvb2tpZSBkZWxldGUgcmVxdWlyZXMgdXJsIG9yIGh0dHAocykgdGFiSWQiKTsKICAgIGNvbnN0IHJlcyA9IGF3YWl0IGNocm9tZS5jb29raWVzLnJlbW92ZSh7IHVybDogYmFzZVVybCwgbmFtZSB9KTsKICAgIHJldHVybiB7IHN1Y2Nlc3M6IEJvb2xlYW4ocmVzKSB9OwogIH0KCiAgaWYgKGFjdGlvbiA9PT0gImRlbGV0ZV9hbGwiKSB7CiAgICBjb25zdCBkb20gPSB0eXBlb2YgcC5kb21haW4gPT09ICJzdHJpbmciID8gcC5kb21haW4udHJpbSgpIDogIiI7CiAgICBpZiAoIWRvbSkgdGhyb3cgbmV3IEVycm9yKCJjb29raWUgZGVsZXRlX2FsbCByZXF1aXJlcyBkb21haW4iKTsKICAgIGNvbnN0IG5vcm1hbGl6ZWQgPSBkb20uc3RhcnRzV2l0aCgiLiIpID8gZG9tIDogYC4ke2RvbX1gOwogICAgY29uc3QgYWxsID0gYXdhaXQgY2hyb21lLmNvb2tpZXMuZ2V0QWxsKHsgZG9tYWluOiBub3JtYWxpemVkIH0pOwogICAgZm9yIChjb25zdCBjIG9mIGFsbCkgewogICAgICBjb25zdCB1ID0gY29va2llUmVtb3ZlVXJsKGMpOwogICAgICBhd2FpdCBjaHJvbWUuY29va2llcy5yZW1vdmUoeyB1cmw6IHUsIG5hbWU6IGMubmFtZSB9KTsKICAgIH0KICAgIHJldHVybiB7IHN1Y2Nlc3M6IHRydWUsIGNvb2tpZTogYWxsLm1hcChzZXJpYWxpemVDb29raWUpIH07CiAgfQoKICB0aHJvdyBuZXcgRXJyb3IoImNvb2tpZV9tYW5hZ2VyOiB1bnN1cHBvcnRlZCBhY3Rpb24iKTsKfQoKLyoqIEBwYXJhbSB7dW5rbm93bn0gcGF5bG9hZCAqLwphc3luYyBmdW5jdGlvbiBoYW5kbGVGaWxsRm9ybShwYXlsb2FkKSB7CiAgY29uc3QgcCA9IGFzUGF5bG9hZChwYXlsb2FkKTsKICBjb25zdCB0YWJJZCA9IGF3YWl0IHJlc29sdmVUYWJJZCh0eXBlb2YgcC50YWJJZCA9PT0gIm51bWJlciIgPyBwLnRhYklkIDogdW5kZWZpbmVkKTsKICBjb25zdCBmaWVsZHMgPSBBcnJheS5pc0FycmF5KHAuZmllbGRzKSA/IHAuZmllbGRzIDogW107CiAgY29uc3QgcmVzID0gYXdhaXQgY2hyb21lLnRhYnMKICAgIC5zZW5kTWVzc2FnZSh0YWJJZCwgewogICAgICB0eXBlOiAiUE9LRV9GSUxMX0ZPUk0iLAogICAgICBmaWVsZHMsCiAgICAgIHN1Ym1pdEFmdGVyOiBwLnN1Ym1pdEFmdGVyID09PSB0cnVlLAogICAgICBzdWJtaXRTZWxlY3RvcjogdHlwZW9mIHAuc3VibWl0U2VsZWN0b3IgPT09ICJzdHJpbmciID8gcC5zdWJtaXRTZWxlY3RvciA6IHVuZGVmaW5lZCwKICAgIH0pCiAgICAuY2F0Y2goKGUpID0+IHsKICAgICAgdGhyb3cgbmV3IEVycm9yKGBmaWxsX2Zvcm0gcmVsYXkgZmFpbGVkOiAke1N0cmluZyhlKX1gKTsKICAgIH0pOwogIHJldHVybiB3aXRoVGFiTWV0YSh0YWJJZCwgcmVzKTsKfQoKLyoqIEBwYXJhbSB7dW5rbm93bn0gcGF5bG9hZCAqLwphc3luYyBmdW5jdGlvbiBoYW5kbGVHZXRTdG9yYWdlKHBheWxvYWQpIHsKICBjb25zdCBwID0gYXNQYXlsb2FkKHBheWxvYWQpOwogIGNvbnN0IHR5cGUgPSBwLnR5cGUgPT09ICJsb2NhbCIgfHwgcC50eXBlID09PSAic2Vzc2lvbiIgfHwgcC50eXBlID09PSAiY29va2llIiA/IHAudHlwZSA6ICJsb2NhbCI7CiAgY29uc3Qga2V5ID0gdHlwZW9mIHAua2V5ID09PSAic3RyaW5nIiA/IHAua2V5IDogdW5kZWZpbmVkOwoKICBpZiAodHlwZSA9PT0gImNvb2tpZSIpIHsKICAgIGNvbnN0IHRhYklkID0gYXdhaXQgcmVzb2x2ZVRhYklkKHR5cGVvZiBwLnRhYklkID09PSAibnVtYmVyIiA/IHAudGFiSWQgOiB1bmRlZmluZWQpOwogICAgY29uc3QgdXJsID0gYXdhaXQgdGFiSHR0cFVybCh0YWJJZCk7CiAgICBjb25zdCBhbGwgPSBhd2FpdCBjaHJvbWUuY29va2llcy5nZXRBbGwoeyB1cmwgfSk7CiAgICAvKiogQHR5cGUge1JlY29yZDxzdHJpbmcsIHN0cmluZz59ICovCiAgICBjb25zdCBkYXRhID0ge307CiAgICBmb3IgKGNvbnN0IGMgb2YgYWxsKSB7CiAgICAgIGlmIChrZXkgJiYgYy5uYW1lICE9PSBrZXkpIGNvbnRpbnVlOwogICAgICBkYXRhW2MubmFtZV0gPSBjLnZhbHVlOwogICAgfQogICAgcmV0dXJuIHsgZGF0YSwgY291bnQ6IE9iamVjdC5rZXlzKGRhdGEpLmxlbmd0aCB9OwogIH0KCiAgY29uc3QgdGFiSWQgPSBhd2FpdCByZXNvbHZlVGFiSWQodHlwZW9mIHAudGFiSWQgPT09ICJudW1iZXIiID8gcC50YWJJZCA6IHVuZGVmaW5lZCk7CiAgY29uc3QgcmVzID0gYXdhaXQgY2hyb21lLnRhYnMKICAgIC5zZW5kTWVzc2FnZSh0YWJJZCwgewogICAgICB0eXBlOiAiUE9LRV9HRVRfU1RPUkFHRSIsCiAgICAgIHN0b3JhZ2VUeXBlOiB0eXBlLAogICAgICBrZXksCiAgICB9KQogICAgLmNhdGNoKChlKSA9PiB7CiAgICAgIHRocm93IG5ldyBFcnJvcihgZ2V0X3N0b3JhZ2UgcmVsYXkgZmFpbGVkOiAke1N0cmluZyhlKX1gKTsKICAgIH0pOwogIHJldHVybiByZXM7Cn0KCi8qKiBAcGFyYW0ge3Vua25vd259IHBheWxvYWQgKi8KYXN5bmMgZnVuY3Rpb24gaGFuZGxlU2V0U3RvcmFnZShwYXlsb2FkKSB7CiAgY29uc3QgcCA9IGFzUGF5bG9hZChwYXlsb2FkKTsKICBjb25zdCB0eXBlID0gcC50eXBlID09PSAibG9jYWwiIHx8IHAudHlwZSA9PT0gInNlc3Npb24iID8gcC50eXBlIDogImxvY2FsIjsKICBjb25zdCBrZXkgPSB0eXBlb2YgcC5rZXkgPT09ICJzdHJpbmciID8gcC5rZXkgOiAiIjsKICBjb25zdCB2YWx1ZSA9IHR5cGVvZiBwLnZhbHVlID09PSAic3RyaW5nIiA/IHAudmFsdWUgOiAiIjsKICBpZiAoIWtleSkgdGhyb3cgbmV3IEVycm9yKCJzZXRfc3RvcmFnZSByZXF1aXJlcyBrZXkiKTsKICBjb25zdCB0YWJJZCA9IGF3YWl0IHJlc29sdmVUYWJJZCh0eXBlb2YgcC50YWJJZCA9PT0gIm51bWJlciIgPyBwLnRhYklkIDogdW5kZWZpbmVkKTsKICBjb25zdCByZXMgPSBhd2FpdCBjaHJvbWUudGFicwogICAgLnNlbmRNZXNzYWdlKHRhYklkLCB7CiAgICAgIHR5cGU6ICJQT0tFX1NFVF9TVE9SQUdFIiwKICAgICAgc3RvcmFnZVR5cGU6IHR5cGUsCiAgICAgIGtleSwKICAgICAgdmFsdWUsCiAgICB9KQogICAgLmNhdGNoKChlKSA9PiB7CiAgICAgIHRocm93IG5ldyBFcnJvcihgc2V0X3N0b3JhZ2UgcmVsYXkgZmFpbGVkOiAke1N0cmluZyhlKX1gKTsKICAgIH0pOwogIHJldHVybiByZXM7Cn0KCi8qKiBAcGFyYW0ge3Vua25vd259IHBheWxvYWQgKi8KYXN5bmMgZnVuY3Rpb24gaGFuZGxlSG92ZXJFbGVtZW50KHBheWxvYWQpIHsKICBjb25zdCBwID0gYXNQYXlsb2FkKHBheWxvYWQpOwogIGNvbnN0IHRhYklkID0gYXdhaXQgcmVzb2x2ZVRhYklkKHR5cGVvZiBwLnRhYklkID09PSAibnVtYmVyIiA/IHAudGFiSWQgOiB1bmRlZmluZWQpOwogIGNvbnN0IHNlbGVjdG9yID0gdHlwZW9mIHAuc2VsZWN0b3IgPT09ICJzdHJpbmciID8gcC5zZWxlY3Rvci50cmltKCkgOiAiIjsKICBjb25zdCB4ID0gdHlwZW9mIHAueCA9PT0gIm51bWJlciIgPyBwLnggOiBOdW1iZXIocC54KTsKICBjb25zdCB5ID0gdHlwZW9mIHAueSA9PT0gIm51bWJlciIgPyBwLnkgOiBOdW1iZXIocC55KTsKICBjb25zdCBoYXNYWSA9IE51bWJlci5pc0Zpbml0ZSh4KSAmJiBOdW1iZXIuaXNGaW5pdGUoeSk7CgogIGlmIChzZWxlY3RvcikgewogICAgY29uc3QgcmVzID0gYXdhaXQgY2hyb21lLnRhYnMuc2VuZE1lc3NhZ2UodGFiSWQsIHsgdHlwZTogIlBPS0VfSE9WRVJfRUxFTUVOVCIsIHNlbGVjdG9yIH0pLmNhdGNoKChlKSA9PiB7CiAgICAgIHRocm93IG5ldyBFcnJvcihgaG92ZXJfZWxlbWVudCByZWxheSBmYWlsZWQ6ICR7U3RyaW5nKGUpfWApOwogICAgfSk7CiAgICByZXR1cm4gd2l0aFRhYk1ldGEodGFiSWQsIHJlcyk7CiAgfQogIGlmIChoYXNYWSkgewogICAgYXdhaXQgc2hvd0N1cnNvckZlZWRiYWNrRG90KHRhYklkLCB4LCB5KTsKICAgIGF3YWl0IGRlYnVnZ2VyQXR0YWNoRm9yVG9vbCh0YWJJZCk7CiAgICB0cnkgewogICAgICBhd2FpdCBkZWJ1Z2dlclNlbmQodGFiSWQsICJJbnB1dC5kaXNwYXRjaE1vdXNlRXZlbnQiLCB7CiAgICAgICAgdHlwZTogIm1vdXNlTW92ZWQiLAogICAgICAgIHgsCiAgICAgICAgeSwKICAgICAgfSk7CiAgICAgIHJldHVybiB3aXRoVGFiTWV0YSh0YWJJZCwgeyBzdWNjZXNzOiB0cnVlIH0pOwogICAgfSBmaW5hbGx5IHsKICAgICAgYXdhaXQgZGVidWdnZXJEZXRhY2hGb3JUb29sKHRhYklkKTsKICAgIH0KICB9CiAgdGhyb3cgbmV3IEVycm9yKCJob3Zlcl9lbGVtZW50IHJlcXVpcmVzIHNlbGVjdG9yIG9yIG51bWVyaWMgeCBhbmQgeSIpOwp9CgovKiogQHBhcmFtIHt1bmtub3dufSBwYXlsb2FkICovCmFzeW5jIGZ1bmN0aW9uIGhhbmRsZU5ld1RhYihwYXlsb2FkKSB7CiAgY29uc3QgcCA9IGFzUGF5bG9hZChwYXlsb2FkKTsKICBjb25zdCB1cmwgPSB0eXBlb2YgcC51cmwgPT09ICJzdHJpbmciICYmIHAudXJsLmxlbmd0aCA+IDAgPyBwLnVybCA6ICJhYm91dDpibGFuayI7CiAgY29uc3QgdGFiID0gYXdhaXQgY2hyb21lLnRhYnMuY3JlYXRlKHsgdXJsLCBhY3RpdmU6IHAuYWN0aXZlICE9PSBmYWxzZSB9KTsKICBpZiAodGFiLmlkID09IG51bGwpIHRocm93IG5ldyBFcnJvcigiRmFpbGVkIHRvIGNyZWF0ZSB0YWIiKTsKICByZXR1cm4geyB0YWJJZDogdGFiLmlkIH07Cn0KCi8qKiBAcGFyYW0ge3Vua25vd259IHBheWxvYWQgKi8KYXN5bmMgZnVuY3Rpb24gaGFuZGxlQ2xvc2VUYWIocGF5bG9hZCkgewogIGNvbnN0IHAgPSBhc1BheWxvYWQocGF5bG9hZCk7CiAgaWYgKHR5cGVvZiBwLnRhYklkICE9PSAibnVtYmVyIiB8fCAhTnVtYmVyLmlzRmluaXRlKHAudGFiSWQpKSB7CiAgICB0aHJvdyBuZXcgRXJyb3IoImNsb3NlX3RhYiByZXF1aXJlcyB0YWJJZCIpOwogIH0KICBhd2FpdCBjaHJvbWUudGFicy5nZXQocC50YWJJZCkuY2F0Y2goKCkgPT4gewogICAgdGhyb3cgbmV3IEVycm9yKGBUYWIgbm90IGZvdW5kOiAke3AudGFiSWR9YCk7CiAgfSk7CiAgYXdhaXQgY2hyb21lLnRhYnMucmVtb3ZlKHAudGFiSWQpOwogIHJldHVybiB7IGNsb3NlZDogdHJ1ZSwgdGFiSWQ6IHAudGFiSWQgfTsKfQoKLyoqIEBwYXJhbSB7dW5rbm93bn0gcGF5bG9hZCAqLwphc3luYyBmdW5jdGlvbiBoYW5kbGVTd2l0Y2hUYWIocGF5bG9hZCkgewogIGNvbnN0IHAgPSBhc1BheWxvYWQocGF5bG9hZCk7CiAgaWYgKHR5cGVvZiBwLnRhYklkICE9PSAibnVtYmVyIiB8fCAhTnVtYmVyLmlzRmluaXRlKHAudGFiSWQpKSB7CiAgICB0aHJvdyBuZXcgRXJyb3IoInN3aXRjaF90YWIgcmVxdWlyZXMgdGFiSWQiKTsKICB9CiAgY29uc3QgdGFiID0gYXdhaXQgY2hyb21lLnRhYnMuZ2V0KHAudGFiSWQpLmNhdGNoKCgpID0+IG51bGwpOwogIGlmICghdGFiPy5pZCkgdGhyb3cgbmV3IEVycm9yKGBUYWIgbm90IGZvdW5kOiAke3AudGFiSWR9YCk7CiAgYXdhaXQgY2hyb21lLnRhYnMudXBkYXRlKHAudGFiSWQsIHsgYWN0aXZlOiB0cnVlIH0pOwogIGlmICh0YWIud2luZG93SWQgIT0gbnVsbCkgewogICAgYXdhaXQgY2hyb21lLndpbmRvd3MudXBkYXRlKHRhYi53aW5kb3dJZCwgeyBmb2N1c2VkOiB0cnVlIH0pOwogIH0KICByZXR1cm4geyB0YWJJZDogcC50YWJJZCwgYWN0aXZlOiB0cnVlIH07Cn0KCi8qKiBAdHlwZSB7UmVjb3JkPHN0cmluZywgKHBheWxvYWQ6IHVua25vd24pID0+IFByb21pc2U8dW5rbm93bj4+fSAqLwpjb25zdCBDT01NQU5EX0hBTkRMRVJTID0gewogIGxpc3RfdGFiczogaGFuZGxlTGlzdFRhYnMsCiAgZ2V0X2FjdGl2ZV90YWI6IGhhbmRsZUdldEFjdGl2ZVRhYiwKICBuYXZpZ2F0ZV90bzogaGFuZGxlTmF2aWdhdGVUbywKICBjbGlja19lbGVtZW50OiBoYW5kbGVDbGlja0VsZW1lbnQsCiAgdHlwZV90ZXh0OiBoYW5kbGVUeXBlVGV4dCwKICBzY3JvbGxfd2luZG93OiBoYW5kbGVTY3JvbGxXaW5kb3csCiAgc2NyZWVuc2hvdDogaGFuZGxlU2NyZWVuc2hvdCwKICBldmFsdWF0ZV9qczogaGFuZGxlRXZhbHVhdGVKcywKICBuZXdfdGFiOiBoYW5kbGVOZXdUYWIsCiAgY2xvc2VfdGFiOiBoYW5kbGVDbG9zZVRhYiwKICBzd2l0Y2hfdGFiOiBoYW5kbGVTd2l0Y2hUYWIsCiAgZ2V0X2RvbV9zbmFwc2hvdDogaGFuZGxlR2V0RG9tU25hcHNob3QsCiAgZ2V0X2FjY2Vzc2liaWxpdHlfdHJlZTogaGFuZGxlR2V0QWNjZXNzaWJpbGl0eVRyZWUsCiAgZmluZF9lbGVtZW50OiBoYW5kbGVGaW5kRWxlbWVudCwKICByZWFkX3BhZ2U6IGhhbmRsZVJlYWRQYWdlLAogIHdhaXRfZm9yX3NlbGVjdG9yOiBoYW5kbGVXYWl0Rm9yU2VsZWN0b3IsCiAgZXhlY3V0ZV9zY3JpcHQ6IGhhbmRsZUV4ZWN1dGVTY3JpcHQsCiAgZ2V0X2NvbnNvbGVfbG9nczogaGFuZGxlR2V0Q29uc29sZUxvZ3MsCiAgY2xlYXJfY29uc29sZV9sb2dzOiBoYW5kbGVDbGVhckNvbnNvbGVMb2dzLAogIGdldF9uZXR3b3JrX2xvZ3M6IGhhbmRsZUdldE5ldHdvcmtMb2dzLAogIGNsZWFyX25ldHdvcmtfbG9nczogaGFuZGxlQ2xlYXJOZXR3b3JrTG9ncywKICBzdGFydF9uZXR3b3JrX2NhcHR1cmU6IGhhbmRsZVN0YXJ0TmV0d29ya0NhcHR1cmUsCiAgc3RvcF9uZXR3b3JrX2NhcHR1cmU6IGhhbmRsZVN0b3BOZXR3b3JrQ2FwdHVyZSwKICBob3Zlcl9lbGVtZW50OiBoYW5kbGVIb3ZlckVsZW1lbnQsCiAgc2NyaXB0X2luamVjdDogaGFuZGxlU2NyaXB0SW5qZWN0LAogIGNvb2tpZV9tYW5hZ2VyOiBoYW5kbGVDb29raWVNYW5hZ2VyLAogIGZpbGxfZm9ybTogaGFuZGxlRmlsbEZvcm0sCiAgZ2V0X3N0b3JhZ2U6IGhhbmRsZUdldFN0b3JhZ2UsCiAgc2V0X3N0b3JhZ2U6IGhhbmRsZVNldFN0b3JhZ2UsCiAgZXJyb3JfcmVwb3J0ZXI6IGhhbmRsZUVycm9yUmVwb3J0ZXIsCiAgZ2V0X3BlcmZvcm1hbmNlX21ldHJpY3M6IGhhbmRsZUdldFBlcmZvcm1hbmNlTWV0cmljcywKICBmdWxsX3BhZ2VfY2FwdHVyZTogaGFuZGxlRnVsbFBhZ2VDYXB0dXJlLAogIHBkZl9leHBvcnQ6IGhhbmRsZVBkZkV4cG9ydCwKICBkZXZpY2VfZW11bGF0ZTogaGFuZGxlRGV2aWNlRW11bGF0ZSwKfTsKCi8qKgogKiBAcGFyYW0ge3N0cmluZ30gY29tbWFuZAogKiBAcGFyYW0ge3Vua25vd259IHBheWxvYWQKICovCmFzeW5jIGZ1bmN0aW9uIGRpc3BhdGNoQ29tbWFuZChjb21tYW5kLCBwYXlsb2FkKSB7CiAgY29uc3QgaGFuZGxlciA9IENPTU1BTkRfSEFORExFUlNbY29tbWFuZF07CiAgaWYgKCFoYW5kbGVyKSB0aHJvdyBuZXcgRXJyb3IoYFVua25vd24gY29tbWFuZDogJHtjb21tYW5kfWApOwogIHJldHVybiBoYW5kbGVyKHBheWxvYWQpOwp9CgpjaHJvbWUucnVudGltZS5vbkluc3RhbGxlZC5hZGRMaXN0ZW5lcigoKSA9PiB7CiAgY2hyb21lLnN0b3JhZ2UubG9jYWwuZ2V0KFsid3NQb3J0IiwgImVuYWJsZWQiXSkudGhlbigodikgPT4gewogICAgY29uc3QgcGF0Y2ggPSB7fTsKICAgIGlmICh2LndzUG9ydCA9PSBudWxsKSBwYXRjaC53c1BvcnQgPSBERUZBVUxUX1dTX1BPUlQ7CiAgICBpZiAodi5lbmFibGVkID09PSB1bmRlZmluZWQpIHBhdGNoLmVuYWJsZWQgPSB0cnVlOwogICAgaWYgKE9iamVjdC5rZXlzKHBhdGNoKS5sZW5ndGgpIGNocm9tZS5zdG9yYWdlLmxvY2FsLnNldChwYXRjaCk7CiAgfSk7CiAgdm9pZCAoYXN5bmMgKCkgPT4gewogICAgaWYgKGF3YWl0IGdldFBva2VFbmFibGVkKCkpIHsKICAgICAgYXdhaXQgZW5zdXJlT2Zmc2NyZWVuQW5kU2NoZWR1bGUoKTsKICAgIH0gZWxzZSB7CiAgICAgIHNjaGVkdWxlS2VlcEFsaXZlQWxhcm0oKTsKICAgIH0KICB9KSgpOwp9KTsKCmNocm9tZS5ydW50aW1lLm9uU3RhcnR1cC5hZGRMaXN0ZW5lcigoKSA9PiB7CiAgdm9pZCAoYXN5bmMgKCkgPT4gewogICAgaWYgKGF3YWl0IGdldFBva2VFbmFibGVkKCkpIHsKICAgICAgYXdhaXQgZW5zdXJlT2Zmc2NyZWVuQW5kU2NoZWR1bGUoKTsKICAgIH0gZWxzZSB7CiAgICAgIHNjaGVkdWxlS2VlcEFsaXZlQWxhcm0oKTsKICAgIH0KICB9KSgpOwp9KTsKCnZvaWQgKGFzeW5jICgpID0+IHsKICBpZiAoYXdhaXQgZ2V0UG9rZUVuYWJsZWQoKSkgewogICAgYXdhaXQgZW5zdXJlT2Zmc2NyZWVuQW5kU2NoZWR1bGUoKTsKICB9IGVsc2UgewogICAgc2NoZWR1bGVLZWVwQWxpdmVBbGFybSgpOwogIH0KfSkoKTsKCi8qKiBAdHlwZSB7UmVjb3JkPHN0cmluZywgKG1lc3NhZ2U6IHVua25vd24sIHNlbmRSZXNwb25zZTogKHI6IHVua25vd24pID0+IHZvaWQpID0+IGJvb2xlYW4gfCB2b2lkPn0gKi8KY29uc3QgUlVOVElNRV9IQU5ETEVSUyA9IHsKICBQT0tFX0dFVF9TVEFURTogKG1lc3NhZ2UsIHNlbmRSZXNwb25zZSkgPT4gewogICAgdm9pZCBQcm9taXNlLmFsbChbZ2V0V3NQb3J0KCksIGNocm9tZS5zdG9yYWdlLmxvY2FsLmdldCgid3NBdXRoVG9rZW4iKV0pLnRoZW4oKFtwb3J0LCBzdF0pID0+IHsKICAgICAgY29uc3QgdG9rID0gc3QgJiYgdHlwZW9mIHN0LndzQXV0aFRva2VuID09PSAic3RyaW5nIiA/IHN0LndzQXV0aFRva2VuIDogIiI7CiAgICAgIHNlbmRSZXNwb25zZSh7CiAgICAgICAgc3RhdHVzOiBtY3BTdGF0dXMsCiAgICAgICAgcG9ydCwKICAgICAgICBsb2c6IGNvbW1hbmRMb2csCiAgICAgICAgaGFzQXV0aFRva2VuOiB0b2subGVuZ3RoID4gMCwKICAgICAgfSk7CiAgICB9KTsKICAgIHJldHVybiB0cnVlOwogIH0sCiAgUE9LRV9TRVRfVE9LRU46IChtZXNzYWdlLCBzZW5kUmVzcG9uc2UpID0+IHsKICAgIGNvbnN0IG0gPSAvKiogQHR5cGUge3sgdG9rZW4/OiB1bmtub3duIH19ICovIChtZXNzYWdlKTsKICAgIGNvbnN0IHRva2VuID0gdHlwZW9mIG0udG9rZW4gPT09ICJzdHJpbmciID8gbS50b2tlbiA6ICIiOwogICAgdm9pZCBjaHJvbWUuc3RvcmFnZS5sb2NhbC5zZXQoeyB3c0F1dGhUb2tlbjogdG9rZW4gfSkudGhlbihhc3luYyAoKSA9PiB7CiAgICAgIGlmIChhd2FpdCBnZXRQb2tlRW5hYmxlZCgpKSB7CiAgICAgICAgYXdhaXQgZW5zdXJlT2Zmc2NyZWVuQW5kU2NoZWR1bGUoKTsKICAgICAgICB0cnkgewogICAgICAgICAgYnJpZGdlUG9ydD8ucG9zdE1lc3NhZ2UoeyB0eXBlOiAicmVjb25uZWN0IiB9KTsKICAgICAgICB9IGNhdGNoIHsKICAgICAgICAgIC8qIGlnbm9yZSAqLwogICAgICAgIH0KICAgICAgfQogICAgICBzZW5kUmVzcG9uc2UoeyBvazogdHJ1ZSB9KTsKICAgIH0pOwogICAgcmV0dXJuIHRydWU7CiAgfSwKICBQT0tFX1NFVF9QT1JUOiAobWVzc2FnZSwgc2VuZFJlc3BvbnNlKSA9PiB7CiAgICBjb25zdCBtID0gLyoqIEB0eXBlIHt7IHBvcnQ/OiB1bmtub3duIH19ICovIChtZXNzYWdlKTsKICAgIGNvbnN0IG5leHQgPSBOdW1iZXIobS5wb3J0KTsKICAgIGlmICghTnVtYmVyLmlzRmluaXRlKG5leHQpIHx8IG5leHQgPD0gMCB8fCBuZXh0ID49IDY1NTM2KSB7CiAgICAgIHNlbmRSZXNwb25zZSh7IG9rOiBmYWxzZSwgZXJyb3I6ICJJbnZhbGlkIHBvcnQiIH0pOwogICAgICByZXR1cm4gZmFsc2U7CiAgICB9CiAgICB2b2lkIGNocm9tZS5zdG9yYWdlLmxvY2FsLnNldCh7IHdzUG9ydDogbmV4dCB9KS50aGVuKGFzeW5jICgpID0+IHsKICAgICAgaWYgKGF3YWl0IGdldFBva2VFbmFibGVkKCkpIHsKICAgICAgICBhd2FpdCBlbnN1cmVPZmZzY3JlZW5BbmRTY2hlZHVsZSgpOwogICAgICAgIHRyeSB7CiAgICAgICAgICBicmlkZ2VQb3J0Py5wb3N0TWVzc2FnZSh7IHR5cGU6ICJyZWNvbm5lY3QiLCBwb3J0OiBuZXh0IH0pOwogICAgICAgIH0gY2F0Y2ggewogICAgICAgICAgLyogaWdub3JlICovCiAgICAgICAgfQogICAgICB9CiAgICAgIHNlbmRSZXNwb25zZSh7IG9rOiB0cnVlLCBwb3J0OiBuZXh0IH0pOwogICAgfSk7CiAgICByZXR1cm4gdHJ1ZTsKICB9LAogIFBPS0VfUkVDT05ORUNUOiAoX21lc3NhZ2UsIHNlbmRSZXNwb25zZSkgPT4gewogICAgdm9pZCAoYXN5bmMgKCkgPT4gewogICAgICBpZiAoYXdhaXQgZ2V0UG9rZUVuYWJsZWQoKSkgewogICAgICAgIGF3YWl0IGVuc3VyZU9mZnNjcmVlbkFuZFNjaGVkdWxlKCk7CiAgICAgICAgdHJ5IHsKICAgICAgICAgIGJyaWRnZVBvcnQ/LnBvc3RNZXNzYWdlKHsgdHlwZTogInJlY29ubmVjdCIgfSk7CiAgICAgICAgfSBjYXRjaCB7CiAgICAgICAgICAvKiBpZ25vcmUgKi8KICAgICAgICB9CiAgICAgIH0KICAgICAgc2VuZFJlc3BvbnNlKHsgb2s6IHRydWUgfSk7CiAgICB9KSgpOwogICAgcmV0dXJuIHRydWU7CiAgfSwKICBQT0tFX0dFVF9BUElfS0VZX1NUQVRFOiAoX21lc3NhZ2UsIHNlbmRSZXNwb25zZSkgPT4gewogICAgdm9pZCBjaHJvbWUuc3RvcmFnZS5sb2NhbC5nZXQoInBva2VBcGlLZXkiKS50aGVuKChzdCkgPT4gewogICAgICBjb25zdCBhcGlLZXkgPSBzdCAmJiB0eXBlb2Ygc3QucG9rZUFwaUtleSA9PT0gInN0cmluZyIgPyBzdC5wb2tlQXBpS2V5LnRyaW0oKSA6ICIiOwogICAgICBzZW5kUmVzcG9uc2UoeyBoYXNBcGlLZXk6IGFwaUtleS5sZW5ndGggPiAwIH0pOwogICAgfSk7CiAgICByZXR1cm4gdHJ1ZTsKICB9LAogIFBPS0VfU0VUX0FQSV9LRVk6IChtZXNzYWdlLCBzZW5kUmVzcG9uc2UpID0+IHsKICAgIGNvbnN0IG0gPSAvKiogQHR5cGUge3sgYXBpS2V5PzogdW5rbm93biB9fSAqLyAobWVzc2FnZSk7CiAgICBjb25zdCBhcGlLZXkgPSB0eXBlb2YgbS5hcGlLZXkgPT09ICJzdHJpbmciID8gbS5hcGlLZXkudHJpbSgpIDogIiI7CiAgICB2b2lkIGNocm9tZS5zdG9yYWdlLmxvY2FsLnNldCh7IHBva2VBcGlLZXk6IGFwaUtleSB9KS50aGVuKCgpID0+IHsKICAgICAgc2VuZFJlc3BvbnNlKHsgb2s6IHRydWUgfSk7CiAgICB9KTsKICAgIHJldHVybiB0cnVlOwogIH0sCiAgUE9LRV9TRU5EX01FU1NBR0U6IChtZXNzYWdlLCBzZW5kUmVzcG9uc2UpID0+IHsKICAgIGNvbnN0IG0gPSAvKiogQHR5cGUge3sgbWVzc2FnZT86IHVua25vd24gfX0gKi8gKG1lc3NhZ2UpOwogICAgY29uc3QgdXNlck1lc3NhZ2UgPSB0eXBlb2YgbS5tZXNzYWdlID09PSAic3RyaW5nIiA/IG0ubWVzc2FnZS50cmltKCkgOiAiIjsKICAgIGlmICghdXNlck1lc3NhZ2UpIHsKICAgICAgc2VuZFJlc3BvbnNlKHsgb2s6IGZhbHNlLCBlcnJvcjogIk1lc3NhZ2UgaXMgcmVxdWlyZWQuIiB9KTsKICAgICAgcmV0dXJuIGZhbHNlOwogICAgfQogICAgdm9pZCAoYXN5bmMgKCkgPT4gewogICAgICBjb25zdCBzdCA9IGF3YWl0IGNocm9tZS5zdG9yYWdlLmxvY2FsLmdldChbInBva2VBcGlLZXkiXSk7CiAgICAgIGNvbnN0IGFwaUtleSA9IHN0ICYmIHR5cGVvZiBzdC5wb2tlQXBpS2V5ID09PSAic3RyaW5nIiA/IHN0LnBva2VBcGlLZXkudHJpbSgpIDogIiI7CiAgICAgIGlmICghYXBpS2V5KSB7CiAgICAgICAgc2VuZFJlc3BvbnNlKHsgb2s6IGZhbHNlLCBlcnJvcjogIk1pc3NpbmcgQVBJIGtleS4gU2F2ZSBpdCBpbiB0aGUgcG9wdXAgZmlyc3QuIiB9KTsKICAgICAgICByZXR1cm47CiAgICAgIH0KICAgICAgdHJ5IHsKICAgICAgICBjb25zdCBwcmltYXJ5UHJvbXB0ID0gYCR7UE9LRV9URVJNSU5BTF9PTkxZX0lOU1RSVUNUSU9OfSR7dXNlck1lc3NhZ2V9YDsKICAgICAgICBjb25zdCBmYWxsYmFja1Byb21wdCA9IGAke1BPS0VfVEVSTUlOQUxfRkFMTEJBQ0tfSU5TVFJVQ1RJT059JHt1c2VyTWVzc2FnZX1gOwoKICAgICAgICAvLyBGaXJzdCBwYXRoOiBsb2NhbGhvc3QgcHJveHkgdGhyb3VnaCBwb2tlLWJyb3dzZXIgTm9kZSBwcm9jZXNzLgogICAgICAgIGxldCBwcmltYXJ5OwogICAgICAgIHRyeSB7CiAgICAgICAgICBwcmltYXJ5ID0gYXdhaXQgcG9zdFBva2VNZXNzYWdlVmlhTG9jYWxQcm94eShhcGlLZXksIHByaW1hcnlQcm9tcHQpOwogICAgICAgIH0gY2F0Y2ggewogICAgICAgICAgLy8gUHJveHkgdW5hdmFpbGFibGU7IGZhbGxiYWNrIHRvIGRpcmVjdCBleHRlbnNpb24gZmV0Y2guCiAgICAgICAgICBwcmltYXJ5ID0gYXdhaXQgcG9zdFBva2VNZXNzYWdlKGFwaUtleSwgcHJpbWFyeVByb21wdCk7CiAgICAgICAgfQoKICAgICAgICBpZiAocHJpbWFyeS5vaykgewogICAgICAgICAgc2VuZFJlc3BvbnNlKHsgb2s6IHRydWUsIGRhdGE6IHByaW1hcnkuZGF0YSB9KTsKICAgICAgICAgIHJldHVybjsKICAgICAgICB9CgogICAgICAgIC8vIElmIGJhY2tlbmQgZmFpbHMgd2l0aCBhIDV4eCwgcmV0cnkgb25jZSB3aXRoIHNob3J0ZXIgc3RyaWN0IGluc3RydWN0aW9uLgogICAgICAgIGlmIChwcmltYXJ5LnN0YXR1cyA+PSA1MDApIHsKICAgICAgICAgIGxldCBmYWxsYmFjazsKICAgICAgICAgIHRyeSB7CiAgICAgICAgICAgIGZhbGxiYWNrID0gYXdhaXQgcG9zdFBva2VNZXNzYWdlVmlhTG9jYWxQcm94eShhcGlLZXksIGZhbGxiYWNrUHJvbXB0KTsKICAgICAgICAgIH0gY2F0Y2ggewogICAgICAgICAgICBmYWxsYmFjayA9IGF3YWl0IHBvc3RQb2tlTWVzc2FnZShhcGlLZXksIGZhbGxiYWNrUHJvbXB0KTsKICAgICAgICAgIH0KICAgICAgICAgIGlmIChmYWxsYmFjay5vaykgewogICAgICAgICAgICBzZW5kUmVzcG9uc2UoewogICAgICAgICAgICAgIG9rOiB0cnVlLAogICAgICAgICAgICAgIGRhdGE6IGZhbGxiYWNrLmRhdGEsCiAgICAgICAgICAgICAgd2FybmluZzogYFByaW1hcnkgcHJvbXB0IGZhaWxlZCB3aXRoICR7cHJpbWFyeS5zdGF0dXN9OyBmYWxsYmFjayBzdWNjZWVkZWQuYCwKICAgICAgICAgICAgfSk7CiAgICAgICAgICAgIHJldHVybjsKICAgICAgICAgIH0KICAgICAgICAgIHNlbmRSZXNwb25zZSh7CiAgICAgICAgICAgIG9rOiBmYWxzZSwKICAgICAgICAgICAgZXJyb3I6CiAgICAgICAgICAgICAgYFBva2UgQVBJIGVycm9yICgke2ZhbGxiYWNrLnN0YXR1c30pLiBgICsKICAgICAgICAgICAgICAoZmFsbGJhY2suc2VydmVyTXNnIHx8IGZhbGxiYWNrLnN0YXR1c1RleHQgfHwgIlVua25vd24gc2VydmVyIGVycm9yLiIpLAogICAgICAgICAgfSk7CiAgICAgICAgICByZXR1cm47CiAgICAgICAgfQoKICAgICAgICBzZW5kUmVzcG9uc2UoewogICAgICAgICAgb2s6IGZhbHNlLAogICAgICAgICAgZXJyb3I6CiAgICAgICAgICAgIGBQb2tlIEFQSSBlcnJvciAoJHtwcmltYXJ5LnN0YXR1c30pLiBgICsKICAgICAgICAgICAgKHByaW1hcnkuc2VydmVyTXNnIHx8IHByaW1hcnkuc3RhdHVzVGV4dCB8fCAiVW5rbm93biBzZXJ2ZXIgZXJyb3IuIiksCiAgICAgICAgfSk7CiAgICAgIH0gY2F0Y2ggKGVycikgewogICAgICAgIHNlbmRSZXNwb25zZSh7CiAgICAgICAgICBvazogZmFsc2UsCiAgICAgICAgICBlcnJvcjogZXJyIGluc3RhbmNlb2YgRXJyb3IgPyBlcnIubWVzc2FnZSA6IFN0cmluZyhlcnIpLAogICAgICAgIH0pOwogICAgICB9CiAgICB9KSgpOwogICAgcmV0dXJuIHRydWU7CiAgfSwKfTsKCmNocm9tZS5ydW50aW1lLm9uTWVzc2FnZS5hZGRMaXN0ZW5lcigobWVzc2FnZSwgX3NlbmRlciwgc2VuZFJlc3BvbnNlKSA9PiB7CiAgaWYgKG1lc3NhZ2UgJiYgdHlwZW9mIG1lc3NhZ2UgPT09ICJvYmplY3QiICYmIG1lc3NhZ2UuYWN0aW9uID09PSAicmVjb25uZWN0IikgewogICAgY29uc3Qgd3NVcmwgPQogICAgICB0eXBlb2YgbWVzc2FnZS53c1VybCA9PT0gInN0cmluZyIgJiYgbWVzc2FnZS53c1VybC50cmltKCkgPyBtZXNzYWdlLndzVXJsLnRyaW0oKSA6ICIiOwogICAgdm9pZCAoYXN5bmMgKCkgPT4gewogICAgICBpZiAod3NVcmwpIHsKICAgICAgICBhd2FpdCBjaHJvbWUuc3RvcmFnZS5sb2NhbC5zZXQoeyB3c1VybCB9KTsKICAgICAgfQogICAgICBpZiAoIShhd2FpdCBnZXRQb2tlRW5hYmxlZCgpKSkgewogICAgICAgIHNlbmRSZXNwb25zZSh7IG9rOiB0cnVlIH0pOwogICAgICAgIHJldHVybjsKICAgICAgfQogICAgICBhd2FpdCBlbnN1cmVPZmZzY3JlZW5BbmRTY2hlZHVsZSgpOwogICAgICB0cnkgewogICAgICAgIGJyaWRnZVBvcnQ/LnBvc3RNZXNzYWdlKHdzVXJsID8geyB0eXBlOiAicmVjb25uZWN0Iiwgd3NVcmwgfSA6IHsgdHlwZTogInJlY29ubmVjdCIgfSk7CiAgICAgIH0gY2F0Y2ggewogICAgICAgIC8qIGlnbm9yZSAqLwogICAgICB9CiAgICAgIHNlbmRSZXNwb25zZSh7IG9rOiB0cnVlIH0pOwogICAgfSkoKTsKICAgIHJldHVybiB0cnVlOwogIH0KICBpZiAobWVzc2FnZSAmJiB0eXBlb2YgbWVzc2FnZSA9PT0gIm9iamVjdCIgJiYgbWVzc2FnZS5hY3Rpb24gPT09ICJzZXRQb2tlQnJvd3NlckVuYWJsZWQiKSB7CiAgICBjb25zdCBlbmFibGVkID0gbWVzc2FnZS5lbmFibGVkID09PSB0cnVlOwogICAgdm9pZCAoYXN5bmMgKCkgPT4gewogICAgICBhd2FpdCBjaHJvbWUuc3RvcmFnZS5sb2NhbC5zZXQoeyBlbmFibGVkIH0pOwogICAgICBpZiAoZW5hYmxlZCkgewogICAgICAgIGF3YWl0IGVuc3VyZU9mZnNjcmVlbkFuZFNjaGVkdWxlKCk7CiAgICAgIH0gZWxzZSB7CiAgICAgICAgYXdhaXQgc3RvcE1jcENvbm5lY3Rpb24oKTsKICAgICAgfQogICAgICBzZW5kUmVzcG9uc2UoeyBvazogdHJ1ZSB9KTsKICAgIH0pKCk7CiAgICByZXR1cm4gdHJ1ZTsKICB9CiAgY29uc3QgdCA9IG1lc3NhZ2UgJiYgdHlwZW9mIG1lc3NhZ2UgPT09ICJvYmplY3QiICYmICJ0eXBlIiBpbiBtZXNzYWdlID8gU3RyaW5nKG1lc3NhZ2UudHlwZSkgOiAiIjsKICBjb25zdCBmbiA9IFJVTlRJTUVfSEFORExFUlNbdF07CiAgaWYgKGZuKSByZXR1cm4gZm4obWVzc2FnZSwgc2VuZFJlc3BvbnNlKTsKICByZXR1cm4gdW5kZWZpbmVkOwp9KTsK
1
+ /** @typedef {{ type: string, requestId?: string, command?: string, payload?: unknown }} WsInbound */
2
+
3
+ const DEFAULT_WS_PORT = 9009;
4
+ const LOG_MAX = 50;
5
+ const NAVIGATE_WAIT_MS = 30_000;
6
+ /** CDP mouseMoved → wait → click so hover menus/tooltips can settle. */
7
+ const CLICK_ELEMENT_HOVER_DELAY_MS = 1000;
8
+ const MAX_NET_PER_TAB = 200;
9
+
10
+ /** @type {Set<number>} */
11
+ const networkCaptureTabs = new Set();
12
+
13
+ /**
14
+ * Per-tab network log: FIFO order of requestIds and merged entries.
15
+ * @type {Map<number, { order: string[]; byId: Map<string, Record<string, unknown>> }>}
16
+ */
17
+ const networkStateByTab = new Map();
18
+
19
+ /**
20
+ * Long-lived port from offscreen.js (holds WebSocket). MV3 service workers cannot keep a socket
21
+ * across suspension; the offscreen document owns the connection.
22
+ * @type {chrome.runtime.Port | null}
23
+ */
24
+ let bridgePort = null;
25
+
26
+ /** @type {"disconnected" | "connecting" | "connected"} */
27
+ let mcpStatus = "disconnected";
28
+
29
+ const KEEPALIVE_ALARM = "keepAlive";
30
+
31
+ /** In-flight offscreen creation; assigned synchronously when the inner async IIFE hits its first `await`. */
32
+ let offscreenCreatingPromise = null;
33
+
34
+ /** @type {Array<{ ts: number, direction: "in" | "out", summary: string }>} */
35
+ let commandLog = [];
36
+
37
+ const POKE_TERMINAL_ONLY_INSTRUCTION =
38
+ "[BROWSER EXTENSION SESSION — MANDATORY]\n" +
39
+ "You are in a browser extension initiated terminal-style session.\n" +
40
+ "DO NOT reply via iMessage. DO NOT reply via SMS. DO NOT reply via Telegram.\n" +
41
+ "DO NOT send any normal chat reply text.\n" +
42
+ "You MUST use browser MCP tools for actions.\n" +
43
+ "The user is probably already on the target page.\n" +
44
+ "Start with a screenshot tool call first to understand current page state.\n" +
45
+ "Continue from the current page and avoid navigate_to unless absolutely needed.\n" +
46
+ "Your only allowed behavior is to execute browser tasks and deliver output through the terminal/tool channel.\n" +
47
+ "Be strict: immediately start the browser process and proceed with the requested browser actions.\n" +
48
+ "[END BROWSER EXTENSION SESSION]\n\n";
49
+
50
+ const POKE_TERMINAL_FALLBACK_INSTRUCTION =
51
+ "[BROWSER SESSION]\n" +
52
+ "No chat reply. No iMessage/SMS/Telegram reply.\n" +
53
+ "Use browser MCP tools. Take screenshot first, continue from current page, avoid navigate_to unless needed.\n" +
54
+ "Start browser actions immediately and respond only through tool/terminal channel.\n\n";
55
+
56
+ function logCommand(direction, summary) {
57
+ commandLog.unshift({ ts: Date.now(), direction, summary });
58
+ if (commandLog.length > LOG_MAX) commandLog.length = LOG_MAX;
59
+ try {
60
+ if (chrome.runtime?.id) {
61
+ const t = new Date();
62
+ const stamp = t.toLocaleTimeString(undefined, { hour12: false });
63
+ const arrow = direction === "in" ? "←" : "→";
64
+ chrome.runtime
65
+ .sendMessage({
66
+ type: "log",
67
+ message: `[${stamp}] ${arrow} ${summary}`,
68
+ })
69
+ .catch(() => {});
70
+ chrome.runtime.sendMessage({ type: "POKE_LOG_UPDATE" }).catch(() => {});
71
+ }
72
+ } catch {
73
+ /* extension context invalidated */
74
+ }
75
+ }
76
+
77
+ /**
78
+ * @param {string} apiKey
79
+ * @param {string} message
80
+ */
81
+ async function postPokeMessage(apiKey, message) {
82
+ const resp = await fetch("https://poke.com/api/v1/inbound/api-message", {
83
+ method: "POST",
84
+ headers: {
85
+ Authorization: `Bearer ${apiKey}`,
86
+ "Content-Type": "application/json",
87
+ },
88
+ body: JSON.stringify({ message }),
89
+ });
90
+
91
+ if (resp.ok) {
92
+ const data = await resp.json().catch(() => null);
93
+ return { ok: true, data };
94
+ }
95
+
96
+ const bodyText = await resp.text();
97
+ let serverMsg = "";
98
+ try {
99
+ const parsed = JSON.parse(bodyText);
100
+ serverMsg =
101
+ typeof parsed?.error === "string"
102
+ ? parsed.error
103
+ : typeof parsed?.message === "string"
104
+ ? parsed.message
105
+ : "";
106
+ } catch {
107
+ /* non-json response body */
108
+ }
109
+
110
+ return {
111
+ ok: false,
112
+ status: resp.status,
113
+ statusText: resp.statusText || "",
114
+ serverMsg: serverMsg || bodyText.slice(0, 300),
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Prefer localhost proxy to avoid extension-origin upstream edge cases.
120
+ * @param {string} apiKey
121
+ * @param {string} message
122
+ */
123
+ async function postPokeMessageViaLocalProxy(apiKey, message) {
124
+ const wsPort = await getWsPort();
125
+ const proxyPort = wsPort + 1;
126
+ const resp = await fetch(`http://127.0.0.1:${proxyPort}/poke/send-message`, {
127
+ method: "POST",
128
+ headers: {
129
+ "Content-Type": "application/json",
130
+ },
131
+ body: JSON.stringify({ apiKey, message }),
132
+ signal: AbortSignal.timeout(3500),
133
+ });
134
+
135
+ if (resp.ok) {
136
+ const data = await resp.json().catch(() => null);
137
+ return { ok: true, data };
138
+ }
139
+
140
+ const bodyText = await resp.text();
141
+ let serverMsg = "";
142
+ try {
143
+ const parsed = JSON.parse(bodyText);
144
+ if (parsed && typeof parsed === "object") {
145
+ const errObj = parsed.error;
146
+ if (errObj && typeof errObj === "object" && typeof errObj.message === "string") {
147
+ serverMsg = errObj.message;
148
+ } else if (typeof parsed.message === "string") {
149
+ serverMsg = parsed.message;
150
+ }
151
+ }
152
+ } catch {
153
+ /* non-json response body */
154
+ }
155
+
156
+ return {
157
+ ok: false,
158
+ status: resp.status,
159
+ statusText: resp.statusText || "",
160
+ serverMsg: serverMsg || bodyText.slice(0, 300),
161
+ };
162
+ }
163
+
164
+ async function getWsPort() {
165
+ const { wsPort } = await chrome.storage.local.get("wsPort");
166
+ if (typeof wsPort === "number" && Number.isFinite(wsPort) && wsPort > 0 && wsPort < 65536) {
167
+ return wsPort;
168
+ }
169
+ return DEFAULT_WS_PORT;
170
+ }
171
+
172
+ async function getPokeEnabled() {
173
+ const { enabled } = await chrome.storage.local.get("enabled");
174
+ if (typeof enabled === "boolean") return enabled;
175
+ return true;
176
+ }
177
+
178
+ async function stopMcpConnection() {
179
+ try {
180
+ if (await chrome.offscreen.hasDocument()) {
181
+ await chrome.offscreen.closeDocument();
182
+ }
183
+ } catch (e) {
184
+ console.error("[poke-browser ext] closeDocument failed:", e);
185
+ }
186
+ bridgePort = null;
187
+ setStatus("disconnected");
188
+ }
189
+
190
+ async function getWsAuthToken() {
191
+ const { wsAuthToken } = await chrome.storage.local.get("wsAuthToken");
192
+ return typeof wsAuthToken === "string" ? wsAuthToken : "";
193
+ }
194
+
195
+ function setStatus(next) {
196
+ mcpStatus = next;
197
+ try {
198
+ if (chrome.runtime?.id) {
199
+ chrome.runtime.sendMessage({ type: "POKE_STATUS", status: next }).catch(() => {});
200
+ }
201
+ } catch {
202
+ /* extension context invalidated */
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Idempotent offscreen creation: concurrent onInstalled / onStartup / alarm / SW wake all await the same work.
208
+ */
209
+ async function setupOffscreen() {
210
+ if (offscreenCreatingPromise) {
211
+ return offscreenCreatingPromise;
212
+ }
213
+
214
+ offscreenCreatingPromise = (async () => {
215
+ try {
216
+ try {
217
+ if (await chrome.offscreen.hasDocument()) {
218
+ return;
219
+ }
220
+ } catch (e) {
221
+ console.error("[poke-browser ext] hasDocument check failed:", e);
222
+ }
223
+
224
+ const stored = await chrome.storage.local.get(["wsPort", "wsUrl"]);
225
+ const storedWsUrl =
226
+ typeof stored.wsUrl === "string" && stored.wsUrl.trim() ? stored.wsUrl.trim() : "";
227
+ const raw = stored.wsPort;
228
+ const port =
229
+ typeof raw === "number" && Number.isFinite(raw) && raw > 0 && raw < 65536
230
+ ? Math.trunc(raw)
231
+ : DEFAULT_WS_PORT;
232
+
233
+ const docUrl = new URL(chrome.runtime.getURL("offscreen.html"));
234
+ if (storedWsUrl) {
235
+ docUrl.searchParams.set("wsUrl", storedWsUrl);
236
+ } else {
237
+ docUrl.searchParams.set("port", String(port));
238
+ }
239
+
240
+ try {
241
+ await chrome.offscreen.createDocument({
242
+ url: docUrl.href,
243
+ reasons: [chrome.offscreen.Reason.DOM_SCRAPING],
244
+ justification:
245
+ "Maintain persistent WebSocket connection to poke-browser MCP server for browser automation",
246
+ });
247
+ console.log("[poke-browser ext] Offscreen document created");
248
+ } catch (err) {
249
+ const msg =
250
+ err && typeof err === "object" && "message" in err
251
+ ? String(/** @type {{ message?: string }} */ (err).message)
252
+ : String(err);
253
+ if (msg.includes("single offscreen document") || msg.includes("already exists")) {
254
+ console.log(
255
+ "[poke-browser ext] Offscreen already exists (concurrent creation), ignoring",
256
+ );
257
+ } else {
258
+ console.error("[poke-browser ext] Failed to create offscreen document:", err);
259
+ throw err;
260
+ }
261
+ }
262
+ } finally {
263
+ offscreenCreatingPromise = null;
264
+ }
265
+ })();
266
+
267
+ return offscreenCreatingPromise;
268
+ }
269
+
270
+ function scheduleKeepAliveAlarm() {
271
+ chrome.alarms.create(KEEPALIVE_ALARM, { periodInMinutes: 0.4 });
272
+ }
273
+
274
+ chrome.alarms.onAlarm.addListener((alarm) => {
275
+ if (alarm.name !== KEEPALIVE_ALARM) return;
276
+ void (async () => {
277
+ if (!(await getPokeEnabled())) return;
278
+ try {
279
+ await setupOffscreen();
280
+ } catch (e) {
281
+ console.error("[poke-browser ext] setupOffscreen failed:", e);
282
+ }
283
+ try {
284
+ bridgePort?.postMessage({ type: "sw_wake" });
285
+ } catch {
286
+ /* ignore */
287
+ }
288
+ })();
289
+ });
290
+
291
+ async function ensureOffscreenAndSchedule() {
292
+ if (!(await getPokeEnabled())) {
293
+ return;
294
+ }
295
+ try {
296
+ await setupOffscreen();
297
+ } catch (e) {
298
+ console.error("[poke-browser ext] setupOffscreen failed:", e);
299
+ }
300
+ scheduleKeepAliveAlarm();
301
+ }
302
+
303
+ chrome.runtime.onConnect.addListener((port) => {
304
+ if (port.name !== "POKE_WS_BRIDGE") return;
305
+ bridgePort = port;
306
+ console.log("[poke-browser ext] Offscreen bridge port connected");
307
+ port.onMessage.addListener((msg) => {
308
+ if (msg && typeof msg === "object" && msg.type === "request_hello_credentials") {
309
+ void getWsAuthToken().then((token) => {
310
+ try {
311
+ port.postMessage({
312
+ type: "hello_credentials",
313
+ token,
314
+ version: chrome.runtime.getManifest().version,
315
+ });
316
+ } catch {
317
+ /* ignore */
318
+ }
319
+ });
320
+ return;
321
+ }
322
+ if (msg && typeof msg === "object" && msg.type === "ws_message" && typeof msg.data === "string") {
323
+ void handleSocketMessage(msg.data).catch((err) => {
324
+ logCommand("in", `Handler error: ${String(err)}`);
325
+ });
326
+ return;
327
+ }
328
+ if (msg && typeof msg === "object" && msg.type === "ws_frame" && typeof msg.raw === "string") {
329
+ void handleSocketMessage(msg.raw).catch((err) => {
330
+ logCommand("in", `Handler error: ${String(err)}`);
331
+ });
332
+ return;
333
+ }
334
+ if (msg && typeof msg === "object" && msg.type === "ws_connected") {
335
+ setStatus("connected");
336
+ return;
337
+ }
338
+ if (msg && typeof msg === "object" && msg.type === "ws_disconnected") {
339
+ setStatus("disconnected");
340
+ return;
341
+ }
342
+ if (msg && typeof msg === "object" && msg.type === "ws_status" && typeof msg.status === "string") {
343
+ setStatus(msg.status);
344
+ return;
345
+ }
346
+ if (
347
+ msg &&
348
+ typeof msg === "object" &&
349
+ msg.type === "ws_log" &&
350
+ typeof msg.direction === "string" &&
351
+ typeof msg.summary === "string"
352
+ ) {
353
+ logCommand(msg.direction, msg.summary);
354
+ }
355
+ });
356
+ port.onDisconnect.addListener(() => {
357
+ if (bridgePort === port) bridgePort = null;
358
+ console.log("[poke-browser ext] Offscreen bridge port disconnected");
359
+ setStatus("disconnected");
360
+ });
361
+ });
362
+
363
+ /**
364
+ * @param {unknown} data
365
+ */
366
+ function safeSend(data) {
367
+ if (!bridgePort) return false;
368
+ try {
369
+ bridgePort.postMessage({ type: "ws_send", payload: data });
370
+ return true;
371
+ } catch {
372
+ return false;
373
+ }
374
+ }
375
+
376
+ /**
377
+ * @param {string} raw
378
+ */
379
+ async function handleSocketMessage(raw) {
380
+ /** @type {WsInbound} */
381
+ let msg;
382
+ try {
383
+ msg = JSON.parse(raw);
384
+ } catch {
385
+ logCommand("in", "Invalid JSON from MCP");
386
+ return;
387
+ }
388
+
389
+ if (msg.type === "auth_ok") {
390
+ console.log("[poke-browser ext] Auth OK received, connection fully established");
391
+ logCommand("out", "WebSocket: auth OK from MCP");
392
+ return;
393
+ }
394
+
395
+ if (msg.type !== "command" || typeof msg.requestId !== "string" || typeof msg.command !== "string") {
396
+ return;
397
+ }
398
+
399
+ logCommand("in", `${msg.command} (${msg.requestId.slice(0, 8)}…)`);
400
+ try {
401
+ const result = await dispatchCommand(msg.command, msg.payload);
402
+ safeSend({ type: "response", requestId: msg.requestId, ok: true, result });
403
+ logCommand("out", `OK ${msg.command}`);
404
+ } catch (err) {
405
+ const error = err instanceof Error ? err.message : String(err);
406
+ safeSend({ type: "response", requestId: msg.requestId, ok: false, error });
407
+ logCommand("out", `ERR ${msg.command}: ${error}`);
408
+ }
409
+ }
410
+
411
+ /**
412
+ * @param {unknown} payload
413
+ */
414
+ function asPayload(payload) {
415
+ return /** @type {Record<string, unknown>} */ (payload && typeof payload === "object" ? payload : {});
416
+ }
417
+
418
+ /**
419
+ * @param {number | undefined} tabId
420
+ */
421
+ async function resolveTabId(tabId) {
422
+ if (typeof tabId === "number" && Number.isFinite(tabId)) {
423
+ const tab = await chrome.tabs.get(tabId).catch(() => null);
424
+ if (!tab) throw new Error(`Tab not found: ${tabId}`);
425
+ return tabId;
426
+ }
427
+ const [active] = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
428
+ if (!active?.id) throw new Error("No active tab");
429
+ return active.id;
430
+ }
431
+
432
+ /**
433
+ * Merge Chrome tab metadata into tool results (tabId, url, title from tabs.get).
434
+ * @param {number} tabId
435
+ * @param {unknown} value
436
+ */
437
+ async function withTabMeta(tabId, value) {
438
+ const tab = await chrome.tabs.get(tabId).catch(() => null);
439
+ const meta = {
440
+ tabId,
441
+ url: tab?.url ?? "",
442
+ title: tab?.title ?? "",
443
+ };
444
+ if (value && typeof value === "object" && !Array.isArray(value)) {
445
+ return { ...(/** @type {Record<string, unknown>} */ (value)), ...meta };
446
+ }
447
+ return { ...meta, value };
448
+ }
449
+
450
+ /**
451
+ * Bring a tab to the foreground so captureVisibleTab targets it.
452
+ * @param {number} tabId
453
+ */
454
+ async function ensureTabVisibleForCapture(tabId) {
455
+ const tab = await chrome.tabs.get(tabId).catch(() => null);
456
+ if (!tab?.id || tab.windowId == null) throw new Error(`Tab not found: ${tabId}`);
457
+ if (!tab.active) {
458
+ await chrome.tabs.update(tabId, { active: true });
459
+ }
460
+ await chrome.windows.update(tab.windowId, { focused: true });
461
+ await new Promise((r) => setTimeout(r, 75));
462
+ return tab;
463
+ }
464
+
465
+ /**
466
+ * @param {number} tabId
467
+ * @param {string} method
468
+ * @param {object} [params]
469
+ */
470
+ function debuggerSend(tabId, method, params = {}) {
471
+ return new Promise((resolve, reject) => {
472
+ chrome.debugger.sendCommand({ tabId }, method, params, () => {
473
+ const err = chrome.runtime.lastError;
474
+ if (err) reject(new Error(err.message));
475
+ else resolve(undefined);
476
+ });
477
+ });
478
+ }
479
+
480
+ /**
481
+ * @param {number} tabId
482
+ * @param {string} method
483
+ * @param {object} [params]
484
+ * @returns {Promise<unknown>}
485
+ */
486
+ function debuggerSendWithResult(tabId, method, params = {}) {
487
+ return new Promise((resolve, reject) => {
488
+ chrome.debugger.sendCommand({ tabId }, method, params, (result) => {
489
+ const err = chrome.runtime.lastError;
490
+ if (err) reject(new Error(err.message));
491
+ else resolve(result);
492
+ });
493
+ });
494
+ }
495
+
496
+ /**
497
+ * @param {number} tabId
498
+ */
499
+ async function debuggerAttach(tabId) {
500
+ await new Promise((resolve, reject) => {
501
+ chrome.debugger.attach({ tabId }, "1.3", () => {
502
+ const err = chrome.runtime.lastError;
503
+ if (err) reject(new Error(err.message));
504
+ else resolve(undefined);
505
+ });
506
+ });
507
+ }
508
+
509
+ /**
510
+ * @param {number} tabId
511
+ */
512
+ async function debuggerDetach(tabId) {
513
+ await new Promise((resolve) => {
514
+ chrome.debugger.detach({ tabId }, () => resolve());
515
+ });
516
+ }
517
+
518
+ /**
519
+ * @param {number} tabId
520
+ */
521
+ function isNetworkCapturing(tabId) {
522
+ return networkCaptureTabs.has(tabId);
523
+ }
524
+
525
+ /**
526
+ * @param {number} tabId
527
+ */
528
+ async function debuggerAttachForTool(tabId) {
529
+ if (isNetworkCapturing(tabId)) return;
530
+ await debuggerAttach(tabId);
531
+ }
532
+
533
+ /**
534
+ * @param {number} tabId
535
+ */
536
+ async function debuggerDetachForTool(tabId) {
537
+ if (isNetworkCapturing(tabId)) return;
538
+ await debuggerDetach(tabId);
539
+ }
540
+
541
+ /**
542
+ * @param {unknown} headers
543
+ * @returns {Record<string, string>}
544
+ */
545
+ function normalizeHeaders(headers) {
546
+ if (!headers || typeof headers !== "object") return {};
547
+ if (Array.isArray(headers)) {
548
+ /** @type {Record<string, string>} */
549
+ const o = {};
550
+ for (const row of headers) {
551
+ if (row && typeof row === "object" && "name" in row) {
552
+ const name = String(/** @type {{ name?: string }} */ (row).name ?? "");
553
+ if (name) o[name] = String(/** @type {{ value?: string }} */ (row).value ?? "");
554
+ }
555
+ }
556
+ return o;
557
+ }
558
+ /** @type {Record<string, string>} */
559
+ const out = {};
560
+ for (const [k, v] of Object.entries(/** @type {Record<string, unknown>} */ (headers))) {
561
+ out[k] = typeof v === "string" ? v : JSON.stringify(v);
562
+ }
563
+ return out;
564
+ }
565
+
566
+ /**
567
+ * @param {number} tabId
568
+ * @param {string} requestId
569
+ * @param {Record<string, unknown>} patch
570
+ */
571
+ function upsertNetworkEntry(tabId, requestId, patch) {
572
+ let state = networkStateByTab.get(tabId);
573
+ if (!state) {
574
+ state = { order: [], byId: new Map() };
575
+ networkStateByTab.set(tabId, state);
576
+ }
577
+ const existing = state.byId.get(requestId);
578
+ if (existing) {
579
+ Object.assign(existing, patch);
580
+ } else {
581
+ state.byId.set(requestId, { requestId, ...patch });
582
+ state.order.push(requestId);
583
+ while (state.order.length > MAX_NET_PER_TAB) {
584
+ const drop = state.order.shift();
585
+ if (drop) state.byId.delete(drop);
586
+ }
587
+ }
588
+ }
589
+
590
+ chrome.debugger.onEvent.addListener((source, method, params) => {
591
+ const tabId = source.tabId;
592
+ if (tabId == null || !networkCaptureTabs.has(tabId)) return;
593
+ const p = params && typeof params === "object" ? /** @type {Record<string, unknown>} */ (params) : {};
594
+ const rid = p.requestId != null ? String(p.requestId) : "";
595
+ if (!rid) return;
596
+
597
+ if (method === "Network.requestWillBeSent") {
598
+ const req = /** @type {{ url?: string; method?: string; headers?: unknown }} */ (p.request ?? {});
599
+ upsertNetworkEntry(tabId, rid, {
600
+ url: req.url ?? "",
601
+ method: req.method ?? "GET",
602
+ requestHeaders: normalizeHeaders(req.headers),
603
+ });
604
+ } else if (method === "Network.responseReceived") {
605
+ const res = /** @type {{ status?: number; mimeType?: string; headers?: unknown; timing?: unknown }} */ (p.response ?? {});
606
+ upsertNetworkEntry(tabId, rid, {
607
+ status: res.status,
608
+ mimeType: res.mimeType,
609
+ responseHeaders: normalizeHeaders(res.headers),
610
+ timing: res.timing ?? null,
611
+ });
612
+ } else if (method === "Network.loadingFinished") {
613
+ upsertNetworkEntry(tabId, rid, {
614
+ bodySize: typeof p.encodedDataLength === "number" ? p.encodedDataLength : undefined,
615
+ loaded: true,
616
+ });
617
+ }
618
+ });
619
+
620
+ chrome.debugger.onDetach.addListener((source, _reason) => {
621
+ if (source.tabId != null) networkCaptureTabs.delete(source.tabId);
622
+ });
623
+
624
+ /**
625
+ * @param {number} tabId
626
+ * @param {number} x
627
+ * @param {number} y
628
+ */
629
+ async function clickViaDebugger(tabId, x, y) {
630
+ await debuggerAttachForTool(tabId);
631
+ try {
632
+ await debuggerSend(tabId, "Input.dispatchMouseEvent", {
633
+ type: "mousePressed",
634
+ x,
635
+ y,
636
+ button: "left",
637
+ clickCount: 1,
638
+ });
639
+ await debuggerSend(tabId, "Input.dispatchMouseEvent", {
640
+ type: "mouseReleased",
641
+ x,
642
+ y,
643
+ button: "left",
644
+ clickCount: 1,
645
+ });
646
+ return { success: true };
647
+ } finally {
648
+ await debuggerDetachForTool(tabId);
649
+ }
650
+ }
651
+
652
+ /**
653
+ * Select-all then Backspace via CDP so the focused field is cleared before typing.
654
+ * @param {number} tabId
655
+ */
656
+ async function clearFocusedFieldViaDebuggerKeys(tabId) {
657
+ const info = await chrome.runtime.getPlatformInfo();
658
+ const mod = info.os === "mac" ? 4 : 2;
659
+ await debuggerSend(tabId, "Input.dispatchKeyEvent", {
660
+ type: "keyDown",
661
+ key: "a",
662
+ code: "KeyA",
663
+ windowsVirtualKeyCode: 65,
664
+ nativeVirtualKeyCode: 65,
665
+ unmodifiedText: "a",
666
+ text: "a",
667
+ modifiers: mod,
668
+ autoRepeat: false,
669
+ });
670
+ await debuggerSend(tabId, "Input.dispatchKeyEvent", {
671
+ type: "keyUp",
672
+ key: "a",
673
+ code: "KeyA",
674
+ windowsVirtualKeyCode: 65,
675
+ nativeVirtualKeyCode: 65,
676
+ unmodifiedText: "a",
677
+ text: "a",
678
+ modifiers: mod,
679
+ });
680
+ await debuggerSend(tabId, "Input.dispatchKeyEvent", {
681
+ type: "keyDown",
682
+ key: "Backspace",
683
+ code: "Backspace",
684
+ windowsVirtualKeyCode: 8,
685
+ nativeVirtualKeyCode: 8,
686
+ unmodifiedText: "",
687
+ text: "",
688
+ modifiers: 0,
689
+ autoRepeat: false,
690
+ });
691
+ await debuggerSend(tabId, "Input.dispatchKeyEvent", {
692
+ type: "keyUp",
693
+ key: "Backspace",
694
+ code: "Backspace",
695
+ windowsVirtualKeyCode: 8,
696
+ nativeVirtualKeyCode: 8,
697
+ unmodifiedText: "",
698
+ text: "",
699
+ modifiers: 0,
700
+ });
701
+ }
702
+
703
+ /**
704
+ * US QWERTY CDP key fields for printable ASCII (Input.dispatchKeyEvent).
705
+ * @typedef {{ key: string, code: string, windowsVirtualKeyCode: number, nativeVirtualKeyCode: number, modifiers: number, unmodifiedText: string, text: string }} CdpPrintableDescriptor
706
+ */
707
+
708
+ /**
709
+ * @param {string} ch Single UTF-16 code unit
710
+ * @returns {CdpPrintableDescriptor}
711
+ */
712
+ function cdpKeyDescriptorForPrintableChar(ch) {
713
+ const cp = ch.codePointAt(0);
714
+
715
+ if (cp >= 97 && cp <= 122) {
716
+ const up = String.fromCharCode(cp - 32);
717
+ return {
718
+ key: ch,
719
+ code: `Key${up}`,
720
+ windowsVirtualKeyCode: up.charCodeAt(0),
721
+ nativeVirtualKeyCode: up.charCodeAt(0),
722
+ modifiers: 0,
723
+ unmodifiedText: ch,
724
+ text: ch,
725
+ };
726
+ }
727
+ if (cp >= 65 && cp <= 90) {
728
+ const low = String.fromCharCode(cp + 32);
729
+ return {
730
+ key: ch,
731
+ code: `Key${ch}`,
732
+ windowsVirtualKeyCode: cp,
733
+ nativeVirtualKeyCode: cp,
734
+ modifiers: 8,
735
+ unmodifiedText: low,
736
+ text: ch,
737
+ };
738
+ }
739
+ if (cp >= 48 && cp <= 57) {
740
+ return {
741
+ key: ch,
742
+ code: `Digit${ch}`,
743
+ windowsVirtualKeyCode: cp,
744
+ nativeVirtualKeyCode: cp,
745
+ modifiers: 0,
746
+ unmodifiedText: ch,
747
+ text: ch,
748
+ };
749
+ }
750
+ if (ch === " ") {
751
+ return {
752
+ key: " ",
753
+ code: "Space",
754
+ windowsVirtualKeyCode: 32,
755
+ nativeVirtualKeyCode: 32,
756
+ modifiers: 0,
757
+ unmodifiedText: " ",
758
+ text: " ",
759
+ };
760
+ }
761
+
762
+ /** @type {Record<string, Omit<CdpPrintableDescriptor, "key">>} */
763
+ const map = {
764
+ "`": {
765
+ code: "Backquote",
766
+ windowsVirtualKeyCode: 192,
767
+ nativeVirtualKeyCode: 192,
768
+ modifiers: 0,
769
+ unmodifiedText: "`",
770
+ text: "`",
771
+ },
772
+ "-": {
773
+ code: "Minus",
774
+ windowsVirtualKeyCode: 189,
775
+ nativeVirtualKeyCode: 189,
776
+ modifiers: 0,
777
+ unmodifiedText: "-",
778
+ text: "-",
779
+ },
780
+ "=": {
781
+ code: "Equal",
782
+ windowsVirtualKeyCode: 187,
783
+ nativeVirtualKeyCode: 187,
784
+ modifiers: 0,
785
+ unmodifiedText: "=",
786
+ text: "=",
787
+ },
788
+ "[": {
789
+ code: "BracketLeft",
790
+ windowsVirtualKeyCode: 219,
791
+ nativeVirtualKeyCode: 219,
792
+ modifiers: 0,
793
+ unmodifiedText: "[",
794
+ text: "[",
795
+ },
796
+ "]": {
797
+ code: "BracketRight",
798
+ windowsVirtualKeyCode: 221,
799
+ nativeVirtualKeyCode: 221,
800
+ modifiers: 0,
801
+ unmodifiedText: "]",
802
+ text: "]",
803
+ },
804
+ "\\": {
805
+ code: "Backslash",
806
+ windowsVirtualKeyCode: 220,
807
+ nativeVirtualKeyCode: 220,
808
+ modifiers: 0,
809
+ unmodifiedText: "\\",
810
+ text: "\\",
811
+ },
812
+ ";": {
813
+ code: "Semicolon",
814
+ windowsVirtualKeyCode: 186,
815
+ nativeVirtualKeyCode: 186,
816
+ modifiers: 0,
817
+ unmodifiedText: ";",
818
+ text: ";",
819
+ },
820
+ "'": {
821
+ code: "Quote",
822
+ windowsVirtualKeyCode: 222,
823
+ nativeVirtualKeyCode: 222,
824
+ modifiers: 0,
825
+ unmodifiedText: "'",
826
+ text: "'",
827
+ },
828
+ ",": {
829
+ code: "Comma",
830
+ windowsVirtualKeyCode: 188,
831
+ nativeVirtualKeyCode: 188,
832
+ modifiers: 0,
833
+ unmodifiedText: ",",
834
+ text: ",",
835
+ },
836
+ ".": {
837
+ code: "Period",
838
+ windowsVirtualKeyCode: 190,
839
+ nativeVirtualKeyCode: 190,
840
+ modifiers: 0,
841
+ unmodifiedText: ".",
842
+ text: ".",
843
+ },
844
+ "/": {
845
+ code: "Slash",
846
+ windowsVirtualKeyCode: 191,
847
+ nativeVirtualKeyCode: 191,
848
+ modifiers: 0,
849
+ unmodifiedText: "/",
850
+ text: "/",
851
+ },
852
+ "!": {
853
+ code: "Digit1",
854
+ windowsVirtualKeyCode: 49,
855
+ nativeVirtualKeyCode: 49,
856
+ modifiers: 8,
857
+ unmodifiedText: "1",
858
+ text: "!",
859
+ },
860
+ "@": {
861
+ code: "Digit2",
862
+ windowsVirtualKeyCode: 50,
863
+ nativeVirtualKeyCode: 50,
864
+ modifiers: 8,
865
+ unmodifiedText: "2",
866
+ text: "@",
867
+ },
868
+ "#": {
869
+ code: "Digit3",
870
+ windowsVirtualKeyCode: 51,
871
+ nativeVirtualKeyCode: 51,
872
+ modifiers: 8,
873
+ unmodifiedText: "3",
874
+ text: "#",
875
+ },
876
+ $: {
877
+ code: "Digit4",
878
+ windowsVirtualKeyCode: 52,
879
+ nativeVirtualKeyCode: 52,
880
+ modifiers: 8,
881
+ unmodifiedText: "4",
882
+ text: "$",
883
+ },
884
+ "%": {
885
+ code: "Digit5",
886
+ windowsVirtualKeyCode: 53,
887
+ nativeVirtualKeyCode: 53,
888
+ modifiers: 8,
889
+ unmodifiedText: "5",
890
+ text: "%",
891
+ },
892
+ "^": {
893
+ code: "Digit6",
894
+ windowsVirtualKeyCode: 54,
895
+ nativeVirtualKeyCode: 54,
896
+ modifiers: 8,
897
+ unmodifiedText: "6",
898
+ text: "^",
899
+ },
900
+ "&": {
901
+ code: "Digit7",
902
+ windowsVirtualKeyCode: 55,
903
+ nativeVirtualKeyCode: 55,
904
+ modifiers: 8,
905
+ unmodifiedText: "7",
906
+ text: "&",
907
+ },
908
+ "*": {
909
+ code: "Digit8",
910
+ windowsVirtualKeyCode: 56,
911
+ nativeVirtualKeyCode: 56,
912
+ modifiers: 8,
913
+ unmodifiedText: "8",
914
+ text: "*",
915
+ },
916
+ "(": {
917
+ code: "Digit9",
918
+ windowsVirtualKeyCode: 57,
919
+ nativeVirtualKeyCode: 57,
920
+ modifiers: 8,
921
+ unmodifiedText: "9",
922
+ text: "(",
923
+ },
924
+ ")": {
925
+ code: "Digit0",
926
+ windowsVirtualKeyCode: 48,
927
+ nativeVirtualKeyCode: 48,
928
+ modifiers: 8,
929
+ unmodifiedText: "0",
930
+ text: ")",
931
+ },
932
+ _: {
933
+ code: "Minus",
934
+ windowsVirtualKeyCode: 189,
935
+ nativeVirtualKeyCode: 189,
936
+ modifiers: 8,
937
+ unmodifiedText: "-",
938
+ text: "_",
939
+ },
940
+ "+": {
941
+ code: "Equal",
942
+ windowsVirtualKeyCode: 187,
943
+ nativeVirtualKeyCode: 187,
944
+ modifiers: 8,
945
+ unmodifiedText: "=",
946
+ text: "+",
947
+ },
948
+ "{": {
949
+ code: "BracketLeft",
950
+ windowsVirtualKeyCode: 219,
951
+ nativeVirtualKeyCode: 219,
952
+ modifiers: 8,
953
+ unmodifiedText: "[",
954
+ text: "{",
955
+ },
956
+ "}": {
957
+ code: "BracketRight",
958
+ windowsVirtualKeyCode: 221,
959
+ nativeVirtualKeyCode: 221,
960
+ modifiers: 8,
961
+ unmodifiedText: "]",
962
+ text: "}",
963
+ },
964
+ "|": {
965
+ code: "Backslash",
966
+ windowsVirtualKeyCode: 220,
967
+ nativeVirtualKeyCode: 220,
968
+ modifiers: 8,
969
+ unmodifiedText: "\\",
970
+ text: "|",
971
+ },
972
+ ":": {
973
+ code: "Semicolon",
974
+ windowsVirtualKeyCode: 186,
975
+ nativeVirtualKeyCode: 186,
976
+ modifiers: 8,
977
+ unmodifiedText: ";",
978
+ text: ":",
979
+ },
980
+ '"': {
981
+ code: "Quote",
982
+ windowsVirtualKeyCode: 222,
983
+ nativeVirtualKeyCode: 222,
984
+ modifiers: 8,
985
+ unmodifiedText: "'",
986
+ text: '"',
987
+ },
988
+ "<": {
989
+ code: "Comma",
990
+ windowsVirtualKeyCode: 188,
991
+ nativeVirtualKeyCode: 188,
992
+ modifiers: 8,
993
+ unmodifiedText: ",",
994
+ text: "<",
995
+ },
996
+ ">": {
997
+ code: "Period",
998
+ windowsVirtualKeyCode: 190,
999
+ nativeVirtualKeyCode: 190,
1000
+ modifiers: 8,
1001
+ unmodifiedText: ".",
1002
+ text: ">",
1003
+ },
1004
+ "?": {
1005
+ code: "Slash",
1006
+ windowsVirtualKeyCode: 191,
1007
+ nativeVirtualKeyCode: 191,
1008
+ modifiers: 8,
1009
+ unmodifiedText: "/",
1010
+ text: "?",
1011
+ },
1012
+ "~": {
1013
+ code: "Backquote",
1014
+ windowsVirtualKeyCode: 192,
1015
+ nativeVirtualKeyCode: 192,
1016
+ modifiers: 8,
1017
+ unmodifiedText: "`",
1018
+ text: "~",
1019
+ },
1020
+ };
1021
+
1022
+ const row = map[ch];
1023
+ if (row) {
1024
+ return { key: ch, ...row };
1025
+ }
1026
+
1027
+ return {
1028
+ key: ch,
1029
+ code: "",
1030
+ windowsVirtualKeyCode: 0,
1031
+ nativeVirtualKeyCode: 0,
1032
+ modifiers: 0,
1033
+ unmodifiedText: ch,
1034
+ text: ch,
1035
+ };
1036
+ }
1037
+
1038
+ /**
1039
+ * @param {number} tabId
1040
+ * @param {string} text
1041
+ * @param {boolean} [clearField]
1042
+ */
1043
+ async function typeTextViaDebugger(tabId, text, clearField) {
1044
+ await debuggerAttachForTool(tabId);
1045
+ try {
1046
+ if (clearField) {
1047
+ await clearFocusedFieldViaDebuggerKeys(tabId);
1048
+ }
1049
+ for (const ch of text) {
1050
+ if (ch === "\r") continue;
1051
+ if (ch === "\n") {
1052
+ const enterBase = {
1053
+ key: "Enter",
1054
+ code: "Enter",
1055
+ windowsVirtualKeyCode: 13,
1056
+ nativeVirtualKeyCode: 13,
1057
+ unmodifiedText: "\r",
1058
+ text: "\r",
1059
+ modifiers: 0,
1060
+ };
1061
+ await debuggerSend(tabId, "Input.dispatchKeyEvent", {
1062
+ type: "keyDown",
1063
+ ...enterBase,
1064
+ autoRepeat: false,
1065
+ });
1066
+ await debuggerSend(tabId, "Input.dispatchKeyEvent", { type: "keyUp", ...enterBase });
1067
+ continue;
1068
+ }
1069
+ const d = cdpKeyDescriptorForPrintableChar(ch);
1070
+ const payload = {
1071
+ key: d.key,
1072
+ code: d.code,
1073
+ windowsVirtualKeyCode: d.windowsVirtualKeyCode,
1074
+ nativeVirtualKeyCode: d.nativeVirtualKeyCode,
1075
+ unmodifiedText: d.unmodifiedText,
1076
+ text: d.text,
1077
+ modifiers: d.modifiers,
1078
+ };
1079
+ await debuggerSend(tabId, "Input.dispatchKeyEvent", {
1080
+ type: "keyDown",
1081
+ ...payload,
1082
+ autoRepeat: false,
1083
+ });
1084
+ await debuggerSend(tabId, "Input.dispatchKeyEvent", { type: "keyUp", ...payload });
1085
+ }
1086
+ return { success: true, charsTyped: text.length };
1087
+ } finally {
1088
+ await debuggerDetachForTool(tabId);
1089
+ }
1090
+ }
1091
+
1092
+ /**
1093
+ * Temporary viewport dot for coordinate-based tools. Serialized into the page by chrome.scripting.
1094
+ * @param {{ x?: unknown; y?: unknown }} p
1095
+ */
1096
+ function pokeInjectedCursorFeedbackDot(p) {
1097
+ const x = typeof p.x === "number" ? p.x : Number(p.x);
1098
+ const y = typeof p.y === "number" ? p.y : Number(p.y);
1099
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return;
1100
+
1101
+ const dot = document.createElement("div");
1102
+ dot.setAttribute("data-poke-cursor-feedback", "1");
1103
+ Object.assign(dot.style, {
1104
+ position: "fixed",
1105
+ left: `${x - 8}px`,
1106
+ top: `${y - 8}px`,
1107
+ width: "16px",
1108
+ height: "16px",
1109
+ borderRadius: "50%",
1110
+ background: "rgb(255, 0, 0)",
1111
+ zIndex: "999999",
1112
+ pointerEvents: "none",
1113
+ opacity: "1",
1114
+ transition: "opacity 600ms ease-out",
1115
+ boxSizing: "border-box",
1116
+ });
1117
+ (document.documentElement || document.body).appendChild(dot);
1118
+ requestAnimationFrame(() => {
1119
+ requestAnimationFrame(() => {
1120
+ dot.style.opacity = "0";
1121
+ });
1122
+ });
1123
+ setTimeout(() => dot.remove(), 650);
1124
+ }
1125
+
1126
+ /**
1127
+ * @param {number} tabId
1128
+ * @param {number} x
1129
+ * @param {number} y
1130
+ */
1131
+ async function showCursorFeedbackDot(tabId, x, y) {
1132
+ try {
1133
+ await chrome.scripting.executeScript({
1134
+ target: { tabId, allFrames: false },
1135
+ world: "MAIN",
1136
+ injectImmediately: true,
1137
+ func: pokeInjectedCursorFeedbackDot,
1138
+ args: [{ x, y }],
1139
+ });
1140
+ } catch {
1141
+ /* chrome:// and other restricted tabs — automation continues */
1142
+ }
1143
+ }
1144
+
1145
+ /**
1146
+ * @param {number} tabId
1147
+ * @param {number} timeoutMs
1148
+ */
1149
+ function waitForTabLoadComplete(tabId, timeoutMs) {
1150
+ return new Promise((resolve, reject) => {
1151
+ const timer = setTimeout(() => {
1152
+ chrome.tabs.onUpdated.removeListener(onUpdated);
1153
+ reject(new Error("navigate_to: load timeout"));
1154
+ }, timeoutMs);
1155
+
1156
+ /**
1157
+ * @param {number} id
1158
+ * @param {chrome.tabs.TabChangeInfo} changeInfo
1159
+ */
1160
+ function onUpdated(id, changeInfo) {
1161
+ if (id !== tabId) return;
1162
+ if (changeInfo.status === "complete") {
1163
+ clearTimeout(timer);
1164
+ chrome.tabs.onUpdated.removeListener(onUpdated);
1165
+ resolve();
1166
+ }
1167
+ }
1168
+
1169
+ chrome.tabs.onUpdated.addListener(onUpdated);
1170
+ });
1171
+ }
1172
+
1173
+ async function handleListTabs() {
1174
+ const tabs = await chrome.tabs.query({});
1175
+ return tabs
1176
+ .filter((t) => t.id != null)
1177
+ .map((t) => ({
1178
+ tabId: t.id,
1179
+ title: t.title ?? "",
1180
+ url: t.url ?? "",
1181
+ active: Boolean(t.active),
1182
+ index: t.index,
1183
+ }));
1184
+ }
1185
+
1186
+ async function handleGetActiveTab() {
1187
+ const [tab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
1188
+ if (!tab?.id) throw new Error("No active tab");
1189
+ return {
1190
+ tabId: tab.id,
1191
+ title: tab.title ?? "",
1192
+ url: tab.url ?? "",
1193
+ active: true,
1194
+ index: tab.index,
1195
+ };
1196
+ }
1197
+
1198
+ /** @param {unknown} payload */
1199
+ async function handleNavigateTo(payload) {
1200
+ const p = asPayload(payload);
1201
+ const url = typeof p.url === "string" ? p.url : "";
1202
+ if (!url) throw new Error("navigate_to requires url");
1203
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1204
+ /** Always wait for chrome.tabs.onUpdated status "complete" so finalUrl/title match the loaded page (not a stale devtools/interstitial URL). */
1205
+ const timeoutMs = p.waitForLoad === false ? 10_000 : NAVIGATE_WAIT_MS;
1206
+ const done = waitForTabLoadComplete(tabId, timeoutMs);
1207
+ await chrome.tabs.update(tabId, { url });
1208
+ await done;
1209
+ const tab = await chrome.tabs.get(tabId);
1210
+ const finalUrl = tab.url ?? "";
1211
+ const title = tab.title ?? "";
1212
+ return {
1213
+ success: true,
1214
+ tabId,
1215
+ url: finalUrl,
1216
+ finalUrl,
1217
+ title,
1218
+ };
1219
+ }
1220
+
1221
+ /** @param {unknown} payload */
1222
+ async function handleClickElement(payload) {
1223
+ const p = asPayload(payload);
1224
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1225
+ const selector = typeof p.selector === "string" ? p.selector.trim() : "";
1226
+ const x = typeof p.x === "number" ? p.x : Number(p.x);
1227
+ const y = typeof p.y === "number" ? p.y : Number(p.y);
1228
+ const hasXY = Number.isFinite(x) && Number.isFinite(y);
1229
+
1230
+ if (selector) {
1231
+ const pt = await chrome.tabs
1232
+ .sendMessage(tabId, { type: "POKE_RESOLVE_CLICK_POINT", selector })
1233
+ .catch((e) => {
1234
+ throw new Error(`click_element resolve failed: ${String(e)}`);
1235
+ });
1236
+ if (
1237
+ !pt ||
1238
+ pt.success !== true ||
1239
+ typeof pt.x !== "number" ||
1240
+ typeof pt.y !== "number" ||
1241
+ !Number.isFinite(pt.x) ||
1242
+ !Number.isFinite(pt.y)
1243
+ ) {
1244
+ const err = pt && typeof pt.error === "string" ? pt.error : "could not resolve target coordinates";
1245
+ throw new Error(`click_element ${err}`);
1246
+ }
1247
+ await showCursorFeedbackDot(tabId, pt.x, pt.y);
1248
+ await debuggerAttachForTool(tabId);
1249
+ try {
1250
+ await debuggerSend(tabId, "Input.dispatchMouseEvent", {
1251
+ type: "mouseMoved",
1252
+ x: pt.x,
1253
+ y: pt.y,
1254
+ });
1255
+ await new Promise((r) => setTimeout(r, CLICK_ELEMENT_HOVER_DELAY_MS));
1256
+ } finally {
1257
+ await debuggerDetachForTool(tabId);
1258
+ }
1259
+ const res = await chrome.tabs.sendMessage(tabId, { type: "POKE_CLICK_ELEMENT", selector }).catch((e) => {
1260
+ throw new Error(`click_element relay failed: ${String(e)}`);
1261
+ });
1262
+ return withTabMeta(tabId, res);
1263
+ }
1264
+ if (hasXY) {
1265
+ await showCursorFeedbackDot(tabId, x, y);
1266
+ const r = await clickViaDebugger(tabId, x, y);
1267
+ return withTabMeta(tabId, r);
1268
+ }
1269
+ throw new Error("click_element requires selector or numeric x and y");
1270
+ }
1271
+
1272
+ /** @param {unknown} payload */
1273
+ async function handleTypeText(payload) {
1274
+ const p = asPayload(payload);
1275
+ const text = typeof p.text === "string" ? p.text : "";
1276
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1277
+ const selector = typeof p.selector === "string" ? p.selector : undefined;
1278
+ const shouldClear = p.clear !== false;
1279
+ const tx = typeof p.x === "number" ? p.x : Number(p.x);
1280
+ const ty = typeof p.y === "number" ? p.y : Number(p.y);
1281
+ const hasXY = Number.isFinite(tx) && Number.isFinite(ty);
1282
+ if (hasXY) await showCursorFeedbackDot(tabId, tx, ty);
1283
+
1284
+ const res = await chrome.tabs
1285
+ .sendMessage(tabId, {
1286
+ type: "POKE_TYPE_TEXT",
1287
+ text,
1288
+ selector,
1289
+ clear: shouldClear,
1290
+ })
1291
+ .catch(() => null);
1292
+
1293
+ if (res && res.success === true) {
1294
+ return withTabMeta(tabId, {
1295
+ success: true,
1296
+ charsTyped: typeof res.charsTyped === "number" ? res.charsTyped : text.length,
1297
+ });
1298
+ }
1299
+ const dbg = await typeTextViaDebugger(tabId, text, shouldClear);
1300
+ return withTabMeta(tabId, dbg);
1301
+ }
1302
+
1303
+ /**
1304
+ * Main-world scroll implementation injected via chrome.scripting (guarantees the target tab frame).
1305
+ * Must be self-contained — Chrome serializes this function into the page.
1306
+ * @param {Record<string, unknown>} p
1307
+ */
1308
+ function pokeInjectedScrollWindow(p) {
1309
+ const behavior = p.behavior === "smooth" ? "smooth" : "auto";
1310
+ const selector = typeof p.selector === "string" ? p.selector.trim() : "";
1311
+
1312
+ /**
1313
+ * @param {string} s
1314
+ * @returns {Element | null}
1315
+ */
1316
+ function querySelectorOrXPath(s) {
1317
+ const t = s.trim();
1318
+ if (t.startsWith("//") || t.toLowerCase().startsWith("xpath:")) {
1319
+ const expr = t.toLowerCase().startsWith("xpath:") ? t.slice(6).trim() : t;
1320
+ try {
1321
+ const r = document.evaluate(expr, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
1322
+ const node = r.singleNodeValue;
1323
+ return node instanceof Element ? node : null;
1324
+ } catch {
1325
+ return null;
1326
+ }
1327
+ }
1328
+ try {
1329
+ return document.querySelector(t);
1330
+ } catch {
1331
+ return null;
1332
+ }
1333
+ }
1334
+
1335
+ const dirRaw = typeof p.direction === "string" ? p.direction.toLowerCase() : "";
1336
+ const dir =
1337
+ dirRaw === "up" || dirRaw === "down" || dirRaw === "left" || dirRaw === "right" ? dirRaw : "";
1338
+
1339
+ try {
1340
+ if (selector) {
1341
+ const el = querySelectorOrXPath(selector);
1342
+ if (!el) {
1343
+ return { success: false, scrollX: window.scrollX, scrollY: window.scrollY, error: "Element not found" };
1344
+ }
1345
+ el.scrollIntoView({ behavior, block: "center", inline: "nearest" });
1346
+ return { success: true, scrollX: window.scrollX, scrollY: window.scrollY };
1347
+ }
1348
+
1349
+ if (typeof p.x === "number" || typeof p.y === "number") {
1350
+ const left = typeof p.x === "number" ? p.x : window.scrollX;
1351
+ const top = typeof p.y === "number" ? p.y : window.scrollY;
1352
+ window.scrollTo({ left, top, behavior });
1353
+ return { success: true, scrollX: window.scrollX, scrollY: window.scrollY };
1354
+ }
1355
+
1356
+ let dx = typeof p.deltaX === "number" && Number.isFinite(p.deltaX) ? p.deltaX : 0;
1357
+ let dy = typeof p.deltaY === "number" && Number.isFinite(p.deltaY) ? p.deltaY : 0;
1358
+
1359
+ if (dir) {
1360
+ let amt = typeof p.amount === "number" && Number.isFinite(p.amount) ? Math.abs(p.amount) : NaN;
1361
+ if (!Number.isFinite(amt) || amt === 0) {
1362
+ if (dir === "up" || dir === "down") {
1363
+ const fromDelta = typeof p.deltaY === "number" && Number.isFinite(p.deltaY) && p.deltaY !== 0;
1364
+ amt = fromDelta ? Math.abs(p.deltaY) : Math.max(200, Math.floor(window.innerHeight * 0.85));
1365
+ } else {
1366
+ const fromDelta = typeof p.deltaX === "number" && Number.isFinite(p.deltaX) && p.deltaX !== 0;
1367
+ amt = fromDelta ? Math.abs(p.deltaX) : Math.max(200, Math.floor(window.innerWidth * 0.85));
1368
+ }
1369
+ }
1370
+ dx = dir === "left" ? -amt : dir === "right" ? amt : 0;
1371
+ dy = dir === "up" ? -amt : dir === "down" ? amt : 0;
1372
+ }
1373
+
1374
+ window.scrollBy({ left: dx, top: dy, behavior });
1375
+ return { success: true, scrollX: window.scrollX, scrollY: window.scrollY };
1376
+ } catch (err) {
1377
+ return { success: false, scrollX: window.scrollX, scrollY: window.scrollY, error: String(err) };
1378
+ }
1379
+ }
1380
+
1381
+ /** @param {unknown} payload */
1382
+ async function handleScrollWindow(payload) {
1383
+ const p = asPayload(payload);
1384
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1385
+ /** Prefer scripting.executeScript so scroll runs in the tab's main frame (not extension/offscreen contexts). */
1386
+ const results = await chrome.scripting.executeScript({
1387
+ target: { tabId, allFrames: false },
1388
+ world: "MAIN",
1389
+ injectImmediately: true,
1390
+ func: pokeInjectedScrollWindow,
1391
+ args: [p],
1392
+ });
1393
+ const res = /** @type {unknown} */ (results[0]?.result);
1394
+ if (res === undefined) {
1395
+ throw new Error("scroll_window: no result from executeScript (tab may be restricted or unavailable)");
1396
+ }
1397
+ return withTabMeta(tabId, res);
1398
+ }
1399
+
1400
+ /** @param {unknown} payload */
1401
+ async function handleScreenshot(payload) {
1402
+ const p = asPayload(payload);
1403
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1404
+ const tab = await ensureTabVisibleForCapture(tabId);
1405
+ const fmt = p.format === "jpeg" ? "jpeg" : "png";
1406
+ const rawQ = typeof p.quality === "number" ? p.quality : 85;
1407
+ /** @type {{ format: 'png' | 'jpeg', quality?: number }} */
1408
+ const opts =
1409
+ fmt === "jpeg"
1410
+ ? { format: "jpeg", quality: Math.min(100, Math.max(0, rawQ)) }
1411
+ : { format: "png" };
1412
+ const dataUrl = await chrome.tabs.captureVisibleTab(tab.windowId, opts);
1413
+ const m = /^data:([^;]+);base64,(.+)$/.exec(dataUrl);
1414
+ if (!m) throw new Error("Invalid screenshot data from browser");
1415
+ return withTabMeta(tabId, {
1416
+ type: "screenshot_result",
1417
+ data: m[2],
1418
+ mimeType: m[1],
1419
+ });
1420
+ }
1421
+
1422
+ /** @param {unknown} payload */
1423
+ async function handleErrorReporter(payload) {
1424
+ const p = asPayload(payload);
1425
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1426
+ const limit = typeof p.limit === "number" ? p.limit : 50;
1427
+ const res = await chrome.tabs
1428
+ .sendMessage(tabId, { type: "POKE_GET_PAGE_ERRORS", limit })
1429
+ .catch((e) => {
1430
+ throw new Error(`error_reporter relay failed: ${String(e)}`);
1431
+ });
1432
+ return withTabMeta(tabId, res);
1433
+ }
1434
+
1435
+ /** @param {unknown} payload */
1436
+ async function handleGetPerformanceMetrics(payload) {
1437
+ const p = asPayload(payload);
1438
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1439
+ await debuggerAttachForTool(tabId);
1440
+ try {
1441
+ const rawMetrics = await debuggerSendWithResult(tabId, "Performance.getMetrics", {});
1442
+ const metricsArr = Array.isArray(rawMetrics)
1443
+ ? rawMetrics
1444
+ : rawMetrics && typeof rawMetrics === "object" && "metrics" in rawMetrics
1445
+ ? /** @type {{ metrics?: unknown }} */ (rawMetrics).metrics
1446
+ : null;
1447
+ /**
1448
+ * @param {string} name
1449
+ */
1450
+ const by = (name) => {
1451
+ if (!Array.isArray(metricsArr)) return undefined;
1452
+ const row = metricsArr.find(
1453
+ (x) => x && typeof x === "object" && /** @type {{ name?: string }} */ (x).name === name,
1454
+ );
1455
+ return row && typeof /** @type {{ value?: number }} */ (row).value === "number"
1456
+ ? /** @type {{ value: number }} */ (row).value
1457
+ : undefined;
1458
+ };
1459
+
1460
+ const navExpr = `(() => {
1461
+ const t = performance.timing;
1462
+ const ns = t.navigationStart || 0;
1463
+ if (!ns) return { domContentLoaded: null, loadEventEnd: null };
1464
+ return {
1465
+ domContentLoaded: t.domContentLoadedEventEnd > 0 ? t.domContentLoadedEventEnd - ns : null,
1466
+ loadEventEnd: t.loadEventEnd > 0 ? t.loadEventEnd - ns : null,
1467
+ };
1468
+ })()`;
1469
+ const navRes = await debuggerSendWithResult(tabId, "Runtime.evaluate", {
1470
+ expression: navExpr,
1471
+ returnByValue: true,
1472
+ });
1473
+ const navVal =
1474
+ navRes && typeof navRes === "object" && "result" in navRes
1475
+ ? /** @type {{ result?: { value?: unknown } }} */ (navRes).result?.value
1476
+ : undefined;
1477
+
1478
+ const paintExpr = `(() => {
1479
+ const entries = performance.getEntriesByType("paint");
1480
+ let firstPaint = null;
1481
+ let firstContentfulPaint = null;
1482
+ for (const e of entries) {
1483
+ if (e.name === "first-paint") firstPaint = e.startTime;
1484
+ if (e.name === "first-contentful-paint") firstContentfulPaint = e.startTime;
1485
+ }
1486
+ return { firstPaint, firstContentfulPaint };
1487
+ })()`;
1488
+ const paintRes = await debuggerSendWithResult(tabId, "Runtime.evaluate", {
1489
+ expression: paintExpr,
1490
+ returnByValue: true,
1491
+ });
1492
+ const paintVal =
1493
+ paintRes && typeof paintRes === "object" && "result" in paintRes
1494
+ ? /** @type {{ result?: { value?: unknown } }} */ (paintRes).result?.value
1495
+ : undefined;
1496
+
1497
+ const nv = navVal && typeof navVal === "object" ? /** @type {Record<string, unknown>} */ (navVal) : {};
1498
+ const pv = paintVal && typeof paintVal === "object" ? /** @type {Record<string, unknown>} */ (paintVal) : {};
1499
+
1500
+ return withTabMeta(tabId, {
1501
+ domContentLoaded: nv.domContentLoaded ?? null,
1502
+ loadEventEnd: nv.loadEventEnd ?? null,
1503
+ firstPaint: pv.firstPaint ?? null,
1504
+ firstContentfulPaint: pv.firstContentfulPaint ?? null,
1505
+ jsHeapUsed: by("JSHeapUsedSize") ?? null,
1506
+ jsHeapTotal: by("JSHeapTotalSize") ?? null,
1507
+ });
1508
+ } finally {
1509
+ await debuggerDetachForTool(tabId);
1510
+ }
1511
+ }
1512
+
1513
+ /**
1514
+ * @param {ArrayBuffer} buffer
1515
+ */
1516
+ function arrayBufferToBase64(buffer) {
1517
+ let binary = "";
1518
+ const bytes = new Uint8Array(buffer);
1519
+ const chunk = 0x8000;
1520
+ for (let i = 0; i < bytes.byteLength; i += chunk) {
1521
+ binary += String.fromCharCode.apply(null, /** @type {number[]} */ (Array.from(bytes.subarray(i, i + chunk))));
1522
+ }
1523
+ return btoa(binary);
1524
+ }
1525
+
1526
+ /**
1527
+ * @param {string[]} dataUrls
1528
+ */
1529
+ async function stitchFullPageScreenshots(dataUrls) {
1530
+ if (dataUrls.length === 0) throw new Error("full_page_capture: no strips");
1531
+ /** @type {ImageBitmap[]} */
1532
+ const bitmaps = [];
1533
+ try {
1534
+ for (const u of dataUrls) {
1535
+ const res = await fetch(u);
1536
+ const blob = await res.blob();
1537
+ const bm = await createImageBitmap(blob);
1538
+ bitmaps.push(bm);
1539
+ }
1540
+ let width = 0;
1541
+ let height = 0;
1542
+ for (const bm of bitmaps) {
1543
+ width = Math.max(width, bm.width);
1544
+ height += bm.height;
1545
+ }
1546
+ const canvas = new OffscreenCanvas(width, height);
1547
+ const ctx = canvas.getContext("2d");
1548
+ if (!ctx) throw new Error("full_page_capture: no 2d context");
1549
+ let y = 0;
1550
+ for (const bm of bitmaps) {
1551
+ ctx.drawImage(bm, 0, y);
1552
+ y += bm.height;
1553
+ }
1554
+ const mimeType = String(dataUrls[0]).startsWith("data:image/jpeg") ? "image/jpeg" : "image/png";
1555
+ const blob = await canvas.convertToBlob({ type: mimeType });
1556
+ const buf = await blob.arrayBuffer();
1557
+ const b64 = arrayBufferToBase64(buf);
1558
+ return `data:${mimeType};base64,${b64}`;
1559
+ } finally {
1560
+ for (const bm of bitmaps) {
1561
+ try {
1562
+ bm.close();
1563
+ } catch {
1564
+ /* ignore */
1565
+ }
1566
+ }
1567
+ }
1568
+ }
1569
+
1570
+ /** @param {unknown} payload */
1571
+ async function handleFullPageCapture(payload) {
1572
+ const p = asPayload(payload);
1573
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1574
+ const tab = await ensureTabVisibleForCapture(tabId);
1575
+ const fmt = p.format === "jpeg" ? "jpeg" : "png";
1576
+ const rawQ = typeof p.quality === "number" ? p.quality : 85;
1577
+ /** @type {{ format: 'png' | 'jpeg', quality?: number }} */
1578
+ const opts =
1579
+ fmt === "jpeg"
1580
+ ? { format: "jpeg", quality: Math.min(100, Math.max(0, rawQ)) }
1581
+ : { format: "png" };
1582
+
1583
+ const info = await chrome.tabs.sendMessage(tabId, { type: "POKE_GET_SCROLL_INFO" }).catch(() => null);
1584
+ if (!info || typeof info !== "object" || typeof /** @type {{ scrollHeight?: unknown }} */ (info).scrollHeight !== "number") {
1585
+ throw new Error("full_page_capture: content script unavailable or invalid scroll info");
1586
+ }
1587
+ const scrollHeight = /** @type {{ scrollHeight: number; innerHeight?: number }} */ (info).scrollHeight;
1588
+ const vh = Math.max(1, Math.floor(/** @type {{ innerHeight?: number }} */ (info).innerHeight || 600));
1589
+
1590
+ /** @type {string[]} */
1591
+ const dataUrls = [];
1592
+ await chrome.tabs.sendMessage(tabId, { type: "POKE_SCROLL_TO", y: 0 });
1593
+ await new Promise((r) => setTimeout(r, 100));
1594
+
1595
+ let y = 0;
1596
+ for (;;) {
1597
+ const dataUrl = await chrome.tabs.captureVisibleTab(tab.windowId, opts);
1598
+ dataUrls.push(dataUrl);
1599
+ if (y + vh >= scrollHeight - 2) break;
1600
+ y = Math.min(y + vh, Math.max(0, scrollHeight - vh));
1601
+ await chrome.tabs.sendMessage(tabId, { type: "POKE_SCROLL_TO", y });
1602
+ await new Promise((r) => setTimeout(r, 120));
1603
+ }
1604
+
1605
+ await chrome.tabs.sendMessage(tabId, { type: "POKE_SCROLL_TO", y: 0 });
1606
+
1607
+ const stitched = await stitchFullPageScreenshots(dataUrls);
1608
+ const m = /^data:([^;]+);base64,(.+)$/.exec(stitched);
1609
+ if (!m) throw new Error("full_page_capture: invalid stitched data URL");
1610
+ return withTabMeta(tabId, {
1611
+ type: "screenshot_result",
1612
+ data: m[2],
1613
+ mimeType: m[1],
1614
+ });
1615
+ }
1616
+
1617
+ /** @param {unknown} payload */
1618
+ async function handlePdfExport(payload) {
1619
+ const p = asPayload(payload);
1620
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1621
+ await ensureTabVisibleForCapture(tabId);
1622
+ await debuggerAttachForTool(tabId);
1623
+ try {
1624
+ const scale = typeof p.scale === "number" && p.scale > 0 ? p.scale : 1;
1625
+ const res = await debuggerSendWithResult(tabId, "Page.printToPDF", {
1626
+ printBackground: true,
1627
+ landscape: p.landscape === true,
1628
+ scale,
1629
+ });
1630
+ const data =
1631
+ res && typeof res === "object" && res !== null && "data" in res
1632
+ ? String(/** @type {{ data?: string }} */ (res).data ?? "")
1633
+ : "";
1634
+ if (!data) throw new Error("pdf_export: printToPDF returned no data");
1635
+ return withTabMeta(tabId, { success: true, data, mimeType: "application/pdf" });
1636
+ } finally {
1637
+ await debuggerDetachForTool(tabId);
1638
+ }
1639
+ }
1640
+
1641
+ const DEVICE_PRESETS = {
1642
+ mobile: { width: 390, height: 844, deviceScaleFactor: 3, mobile: true },
1643
+ tablet: { width: 834, height: 1112, deviceScaleFactor: 2, mobile: true },
1644
+ desktop: { width: 1280, height: 800, deviceScaleFactor: 1, mobile: false },
1645
+ };
1646
+
1647
+ /** @param {unknown} payload */
1648
+ async function handleDeviceEmulate(payload) {
1649
+ const p = asPayload(payload);
1650
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1651
+ const d = p.device === "mobile" || p.device === "tablet" || p.device === "desktop" ? p.device : "desktop";
1652
+ const preset = DEVICE_PRESETS[d];
1653
+ const width = typeof p.width === "number" ? p.width : preset.width;
1654
+ const height = typeof p.height === "number" ? p.height : preset.height;
1655
+ const deviceScaleFactor =
1656
+ typeof p.deviceScaleFactor === "number" ? p.deviceScaleFactor : preset.deviceScaleFactor;
1657
+
1658
+ await debuggerAttachForTool(tabId);
1659
+ try {
1660
+ await debuggerSend(tabId, "Emulation.setDeviceMetricsOverride", {
1661
+ width: Math.round(width),
1662
+ height: Math.round(height),
1663
+ deviceScaleFactor,
1664
+ mobile: preset.mobile,
1665
+ fitWindow: false,
1666
+ scale: 1,
1667
+ });
1668
+ const ua = typeof p.userAgent === "string" && p.userAgent.trim() ? p.userAgent.trim() : undefined;
1669
+ if (ua) {
1670
+ await debuggerSend(tabId, "Network.setUserAgentOverride", { userAgent: ua });
1671
+ }
1672
+ return withTabMeta(tabId, { success: true });
1673
+ } finally {
1674
+ await debuggerDetachForTool(tabId);
1675
+ }
1676
+ }
1677
+
1678
+ /** @param {unknown} payload */
1679
+ async function handleEvaluateJs(payload) {
1680
+ const p = asPayload(payload);
1681
+ const code = typeof p.code === "string" ? p.code : "";
1682
+ if (!code) throw new Error("evaluate_js requires code");
1683
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1684
+ const requestId =
1685
+ typeof p.requestId === "string" ? p.requestId : `bg-${Date.now()}-${Math.random().toString(16).slice(2)}`;
1686
+ const timeoutMs = typeof p.timeoutMs === "number" ? p.timeoutMs : 30000;
1687
+ const res = await chrome.tabs.sendMessage(tabId, {
1688
+ type: "POKE_EVAL",
1689
+ code,
1690
+ requestId,
1691
+ timeoutMs,
1692
+ }).catch((e) => {
1693
+ throw new Error(`evaluate_js relay failed: ${String(e)}`);
1694
+ });
1695
+ return withTabMeta(tabId, res);
1696
+ }
1697
+
1698
+ /**
1699
+ * @param {number} tabId
1700
+ * @param {string} pokeType
1701
+ * @param {Record<string, unknown>} data
1702
+ */
1703
+ async function sendPerceptionToTab(tabId, pokeType, data) {
1704
+ const res = await chrome.tabs.sendMessage(tabId, { ...data, type: pokeType }).catch((e) => {
1705
+ throw new Error(`Perception relay failed (${pokeType}): ${String(e)}`);
1706
+ });
1707
+ if (res && typeof res === "object" && "error" in res && typeof res.error === "string") {
1708
+ throw new Error(res.error);
1709
+ }
1710
+ return res;
1711
+ }
1712
+
1713
+ /** @param {unknown} payload */
1714
+ async function handleGetDomSnapshot(payload) {
1715
+ const p = asPayload(payload);
1716
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1717
+ const res = await sendPerceptionToTab(tabId, "POKE_GET_DOM_SNAPSHOT", {
1718
+ includeHidden: p.includeHidden === true,
1719
+ maxDepth: typeof p.maxDepth === "number" ? p.maxDepth : undefined,
1720
+ });
1721
+ return withTabMeta(tabId, res);
1722
+ }
1723
+
1724
+ /** @param {unknown} payload */
1725
+ async function handleGetAccessibilityTree(payload) {
1726
+ const p = asPayload(payload);
1727
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1728
+ const res = await sendPerceptionToTab(tabId, "POKE_GET_A11Y_TREE", {
1729
+ interactiveOnly: p.interactiveOnly === true,
1730
+ });
1731
+ return withTabMeta(tabId, res);
1732
+ }
1733
+
1734
+ /** @param {unknown} payload */
1735
+ async function handleFindElement(payload) {
1736
+ const p = asPayload(payload);
1737
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1738
+ const query = typeof p.query === "string" ? p.query : "";
1739
+ const strategy =
1740
+ p.strategy === "css" || p.strategy === "text" || p.strategy === "aria" || p.strategy === "xpath"
1741
+ ? p.strategy
1742
+ : "auto";
1743
+ const res = await sendPerceptionToTab(tabId, "POKE_FIND_ELEMENT", { query, strategy });
1744
+ return withTabMeta(tabId, res);
1745
+ }
1746
+
1747
+ /** @param {unknown} payload */
1748
+ async function handleReadPage(payload) {
1749
+ const p = asPayload(payload);
1750
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1751
+ const format =
1752
+ p.format === "markdown" || p.format === "text" || p.format === "structured" ? p.format : "structured";
1753
+ const res = await sendPerceptionToTab(tabId, "POKE_READ_PAGE", { format });
1754
+ return withTabMeta(tabId, res);
1755
+ }
1756
+
1757
+ /** @param {unknown} payload */
1758
+ async function handleWaitForSelector(payload) {
1759
+ const p = asPayload(payload);
1760
+ const selector = typeof p.selector === "string" ? p.selector : "";
1761
+ if (!selector.trim()) throw new Error("wait_for_selector requires selector");
1762
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1763
+ const timeout = typeof p.timeout === "number" && p.timeout > 0 ? p.timeout : 10000;
1764
+ const visible = p.visible === true;
1765
+ const res = await chrome.tabs
1766
+ .sendMessage(tabId, { type: "POKE_WAIT_FOR_SELECTOR", selector, timeout, visible })
1767
+ .catch((e) => {
1768
+ throw new Error(`wait_for_selector relay failed: ${String(e)}`);
1769
+ });
1770
+ return withTabMeta(tabId, res);
1771
+ }
1772
+
1773
+ /** @param {unknown} payload */
1774
+ async function handleExecuteScript(payload) {
1775
+ const p = asPayload(payload);
1776
+ const script = typeof p.script === "string" ? p.script : "";
1777
+ if (!script.trim()) throw new Error("execute_script requires script");
1778
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1779
+ const args = Array.isArray(p.args) ? p.args : [];
1780
+
1781
+ const results = await chrome.scripting.executeScript({
1782
+ target: { tabId, allFrames: false },
1783
+ world: "MAIN",
1784
+ injectImmediately: true,
1785
+ func: async (scriptSource, callArgs) => {
1786
+ const seen = new WeakSet();
1787
+ /**
1788
+ * @param {string} _k
1789
+ * @param {unknown} val
1790
+ */
1791
+ function replacer(_k, val) {
1792
+ if (typeof val === "bigint") return val.toString();
1793
+ if (typeof val === "object" && val !== null) {
1794
+ if (seen.has(/** @type {object} */ (val))) return "[Circular]";
1795
+ seen.add(/** @type {object} */ (val));
1796
+ }
1797
+ return val;
1798
+ }
1799
+ try {
1800
+ const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
1801
+ const fn = new AsyncFunction("args", `return (async () => {\n${scriptSource}\n})();`);
1802
+ const raw = await fn(callArgs ?? []);
1803
+ try {
1804
+ return { result: JSON.parse(JSON.stringify(raw, replacer)) };
1805
+ } catch (serErr) {
1806
+ return {
1807
+ result: String(raw),
1808
+ error: `serialization: ${serErr instanceof Error ? serErr.message : String(serErr)}`,
1809
+ };
1810
+ }
1811
+ } catch (e) {
1812
+ return { error: String(e) };
1813
+ }
1814
+ },
1815
+ args: [script, args],
1816
+ });
1817
+
1818
+ const fr = /** @type {{ result?: unknown; error?: string } | undefined} */ (results[0]?.result);
1819
+ if (!fr) return withTabMeta(tabId, { result: null, error: "No frame result" });
1820
+ if (typeof fr.error === "string" && fr.error && fr.result === undefined) {
1821
+ return withTabMeta(tabId, { result: undefined, error: fr.error });
1822
+ }
1823
+ return withTabMeta(tabId, {
1824
+ result: fr.result,
1825
+ error: typeof fr.error === "string" ? fr.error : undefined,
1826
+ });
1827
+ }
1828
+
1829
+ /** @param {unknown} payload */
1830
+ async function handleGetConsoleLogs(payload) {
1831
+ const p = asPayload(payload);
1832
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1833
+ const level =
1834
+ p.level === "error" || p.level === "warn" || p.level === "info" || p.level === "log" || p.level === "all"
1835
+ ? p.level
1836
+ : "all";
1837
+ const limit = typeof p.limit === "number" ? Math.min(500, Math.max(1, p.limit)) : 100;
1838
+ const res = await chrome.tabs
1839
+ .sendMessage(tabId, { type: "POKE_GET_CONSOLE_LOGS", level, limit })
1840
+ .catch((e) => {
1841
+ throw new Error(`get_console_logs relay failed: ${String(e)}`);
1842
+ });
1843
+ const logs = res && typeof res === "object" && "logs" in res ? /** @type {{ logs: unknown }} */ (res).logs : [];
1844
+ const count = res && typeof res === "object" && "count" in res ? Number(/** @type {{ count?: number }} */ (res).count) : 0;
1845
+ return { logs, count, tabId };
1846
+ }
1847
+
1848
+ /** @param {unknown} payload */
1849
+ async function handleClearConsoleLogs(payload) {
1850
+ const p = asPayload(payload);
1851
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1852
+ await chrome.tabs.sendMessage(tabId, { type: "POKE_CLEAR_CONSOLE_LOGS" }).catch((e) => {
1853
+ throw new Error(`clear_console_logs relay failed: ${String(e)}`);
1854
+ });
1855
+ return { cleared: true, tabId };
1856
+ }
1857
+
1858
+ /** @param {unknown} payload */
1859
+ async function handleStartNetworkCapture(payload) {
1860
+ const p = asPayload(payload);
1861
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1862
+ networkStateByTab.delete(tabId);
1863
+ if (!networkCaptureTabs.has(tabId)) {
1864
+ await debuggerAttach(tabId);
1865
+ networkCaptureTabs.add(tabId);
1866
+ }
1867
+ await debuggerSend(tabId, "Network.enable", {});
1868
+ return { success: true, tabId, capturing: true };
1869
+ }
1870
+
1871
+ /** @param {unknown} payload */
1872
+ async function handleStopNetworkCapture(payload) {
1873
+ const p = asPayload(payload);
1874
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1875
+ if (!networkCaptureTabs.has(tabId)) {
1876
+ return { success: true, tabId, capturing: false };
1877
+ }
1878
+ try {
1879
+ await debuggerSend(tabId, "Network.disable", {});
1880
+ } catch {
1881
+ /* ignore */
1882
+ }
1883
+ networkCaptureTabs.delete(tabId);
1884
+ await debuggerDetach(tabId);
1885
+ return { success: true, tabId, capturing: false };
1886
+ }
1887
+
1888
+ /** @param {unknown} payload */
1889
+ async function handleGetNetworkLogs(payload) {
1890
+ const p = asPayload(payload);
1891
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1892
+ const filter = typeof p.filter === "string" ? p.filter : "";
1893
+ const limit = typeof p.limit === "number" ? Math.min(200, Math.max(1, p.limit)) : 50;
1894
+ const includeBody = p.includeBody === true;
1895
+
1896
+ const state = networkStateByTab.get(tabId);
1897
+ if (!state) {
1898
+ return { requests: [], count: 0 };
1899
+ }
1900
+
1901
+ /** @type {Record<string, unknown>[]} */
1902
+ const rows = [];
1903
+ for (const rid of state.order) {
1904
+ const row = state.byId.get(rid);
1905
+ if (row) rows.push(row);
1906
+ }
1907
+ let filtered = filter ? rows.filter((r) => String(r.url ?? "").includes(filter)) : [...rows];
1908
+ filtered = filtered.slice(-limit);
1909
+
1910
+ const needTempAttach = includeBody && !networkCaptureTabs.has(tabId);
1911
+ if (needTempAttach) {
1912
+ await debuggerAttach(tabId);
1913
+ }
1914
+ try {
1915
+ /** @type {Record<string, unknown>[]} */
1916
+ const out = [];
1917
+ for (const e of filtered) {
1918
+ const copy = { ...e };
1919
+ if (includeBody && e.loaded === true && typeof e.requestId === "string") {
1920
+ try {
1921
+ const bodyRes = /** @type {{ body?: string; base64Encoded?: boolean }} */ (
1922
+ await debuggerSendWithResult(tabId, "Network.getResponseBody", { requestId: e.requestId })
1923
+ );
1924
+ copy.body = bodyRes.body;
1925
+ copy.bodyBase64Encoded = bodyRes.base64Encoded === true;
1926
+ } catch {
1927
+ copy.bodyFetchError = "Network.getResponseBody failed";
1928
+ }
1929
+ }
1930
+ out.push(copy);
1931
+ }
1932
+ return { requests: out, count: out.length };
1933
+ } finally {
1934
+ if (needTempAttach) {
1935
+ await debuggerDetach(tabId);
1936
+ }
1937
+ }
1938
+ }
1939
+
1940
+ /** @param {unknown} payload */
1941
+ async function handleClearNetworkLogs(payload) {
1942
+ const p = asPayload(payload);
1943
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
1944
+ networkStateByTab.delete(tabId);
1945
+ return { cleared: true, tabId };
1946
+ }
1947
+
1948
+ const PERSISTENT_LOADER_ID = "poke-browser-persistent-loader";
1949
+
1950
+ /**
1951
+ * @param {chrome.cookies.Cookie} c
1952
+ */
1953
+ function cookieRemoveUrl(c) {
1954
+ const dom = c.domain.startsWith(".") ? c.domain.slice(1) : c.domain;
1955
+ const scheme = c.secure ? "https" : "http";
1956
+ const path = c.path && c.path.length ? c.path : "/";
1957
+ return `${scheme}://${dom}${path}`;
1958
+ }
1959
+
1960
+ /**
1961
+ * @param {chrome.cookies.Cookie} c
1962
+ */
1963
+ function serializeCookie(c) {
1964
+ return {
1965
+ name: c.name,
1966
+ value: c.value,
1967
+ domain: c.domain,
1968
+ path: c.path,
1969
+ secure: c.secure,
1970
+ httpOnly: c.httpOnly,
1971
+ sameSite: c.sameSite,
1972
+ expirationDate: c.expirationDate,
1973
+ session: c.session,
1974
+ };
1975
+ }
1976
+
1977
+ async function ensurePersistentLoaderRegistered() {
1978
+ const existing = await chrome.scripting.getRegisteredContentScripts({ ids: [PERSISTENT_LOADER_ID] });
1979
+ if (Array.isArray(existing) && existing.length > 0) return;
1980
+ await chrome.scripting.registerContentScripts([
1981
+ {
1982
+ id: PERSISTENT_LOADER_ID,
1983
+ matches: ["<all_urls>"],
1984
+ js: ["persistent-loader.js"],
1985
+ runAt: "document_start",
1986
+ },
1987
+ ]);
1988
+ }
1989
+
1990
+ /**
1991
+ * @param {number} tabId
1992
+ */
1993
+ async function tabHttpUrl(tabId) {
1994
+ const tab = await chrome.tabs.get(tabId);
1995
+ const u = tab.url ?? "";
1996
+ if (!u.startsWith("http://") && !u.startsWith("https://")) {
1997
+ throw new Error("Tab must have an http(s) URL for this operation");
1998
+ }
1999
+ return u;
2000
+ }
2001
+
2002
+ /** @param {unknown} payload */
2003
+ async function handleScriptInject(payload) {
2004
+ const p = asPayload(payload);
2005
+ const script = typeof p.script === "string" ? p.script : "";
2006
+ if (!script.trim()) throw new Error("script_inject requires script");
2007
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
2008
+ await tabHttpUrl(tabId);
2009
+ const persistent = p.persistent === true;
2010
+ const runAt =
2011
+ p.runAt === "document_start" || p.runAt === "document_end" || p.runAt === "document_idle"
2012
+ ? p.runAt
2013
+ : "document_idle";
2014
+
2015
+ if (persistent) {
2016
+ const tab = await chrome.tabs.get(tabId);
2017
+ const url = tab.url ?? "";
2018
+ const u = new URL(url);
2019
+ const matchPattern = `${u.origin}/*`;
2020
+ const injectionId = `poke-${crypto.randomUUID()}`;
2021
+ const got = await chrome.storage.local.get("pokePersistentInjections");
2022
+ const list = Array.isArray(got.pokePersistentInjections) ? got.pokePersistentInjections : [];
2023
+ list.push({ id: injectionId, matchPattern, script, runAt });
2024
+ await chrome.storage.local.set({ pokePersistentInjections: list });
2025
+ await ensurePersistentLoaderRegistered();
2026
+ return withTabMeta(tabId, { success: true, injectionId });
2027
+ }
2028
+
2029
+ if (runAt === "document_idle") {
2030
+ const res = await chrome.tabs.sendMessage(tabId, { type: "POKE_SCRIPT_INJECT", script }).catch((e) => {
2031
+ throw new Error(`script_inject relay failed: ${String(e)}`);
2032
+ });
2033
+ return withTabMeta(tabId, { success: Boolean(res && res.success === true) });
2034
+ }
2035
+
2036
+ await chrome.scripting.executeScript({
2037
+ target: { tabId, allFrames: false },
2038
+ world: "MAIN",
2039
+ injectImmediately: runAt === "document_start",
2040
+ func: (code) => {
2041
+ const s = document.createElement("script");
2042
+ s.textContent = code;
2043
+ const r = document.documentElement || document.head || document.body;
2044
+ if (r) {
2045
+ r.appendChild(s);
2046
+ s.remove();
2047
+ }
2048
+ },
2049
+ args: [script],
2050
+ });
2051
+ return withTabMeta(tabId, { success: true });
2052
+ }
2053
+
2054
+ /** @param {unknown} payload */
2055
+ async function handleCookieManager(payload) {
2056
+ const p = asPayload(payload);
2057
+ const action =
2058
+ p.action === "get" || p.action === "get_all" || p.action === "set" || p.action === "delete" || p.action === "delete_all"
2059
+ ? p.action
2060
+ : null;
2061
+ if (!action) throw new Error("cookie_manager requires action");
2062
+
2063
+ const tabId =
2064
+ typeof p.tabId === "number" && Number.isFinite(p.tabId) ? await resolveTabId(p.tabId) : undefined;
2065
+
2066
+ /** @type {string | undefined} */
2067
+ let baseUrl = typeof p.url === "string" && p.url.length > 0 ? p.url : undefined;
2068
+ if (!baseUrl && tabId != null) {
2069
+ try {
2070
+ baseUrl = await tabHttpUrl(tabId);
2071
+ } catch {
2072
+ /* tab may be invalid for http(s); leave baseUrl unset */
2073
+ }
2074
+ }
2075
+
2076
+ if (action === "get") {
2077
+ const name = typeof p.name === "string" ? p.name : "";
2078
+ if (!name) throw new Error("cookie get requires name");
2079
+ if (!baseUrl) throw new Error("cookie get requires url or http(s) tabId");
2080
+ const c = await chrome.cookies.get({ url: baseUrl, name });
2081
+ return { success: true, cookie: c ? serializeCookie(c) : undefined };
2082
+ }
2083
+
2084
+ if (action === "get_all") {
2085
+ /** @type {chrome.cookies.GetAllDetails} */
2086
+ const q = {};
2087
+ if (baseUrl) q.url = baseUrl;
2088
+ const dom = typeof p.domain === "string" && p.domain.length > 0 ? p.domain : undefined;
2089
+ if (dom) q.domain = dom;
2090
+ if (!q.url && !q.domain) throw new Error("cookie get_all requires url/domain or http(s) tabId");
2091
+ const all = await chrome.cookies.getAll(q);
2092
+ const cookies = all.map(serializeCookie);
2093
+ return { success: true, cookie: cookies };
2094
+ }
2095
+
2096
+ if (action === "set") {
2097
+ const name = typeof p.name === "string" ? p.name : "";
2098
+ if (!name) throw new Error("cookie set requires name");
2099
+ const value = typeof p.value === "string" ? p.value : "";
2100
+ if (!baseUrl && typeof p.domain !== "string") {
2101
+ throw new Error("cookie set requires url or tab with http(s) URL, or domain");
2102
+ }
2103
+ /** @type {chrome.cookies.SetDetails} */
2104
+ const details = { name, value };
2105
+ if (baseUrl) details.url = baseUrl;
2106
+ if (typeof p.domain === "string") details.domain = p.domain;
2107
+ if (typeof p.path === "string") details.path = p.path;
2108
+ if (p.secure === true) details.secure = true;
2109
+ if (p.httpOnly === true) details.httpOnly = true;
2110
+ if (typeof p.expirationDate === "number") details.expirationDate = p.expirationDate;
2111
+ const c = await chrome.cookies.set(details);
2112
+ if (!c) return { success: false, cookie: undefined };
2113
+ return { success: true, cookie: serializeCookie(c) };
2114
+ }
2115
+
2116
+ if (action === "delete") {
2117
+ const name = typeof p.name === "string" ? p.name : "";
2118
+ if (!name) throw new Error("cookie delete requires name");
2119
+ if (!baseUrl) throw new Error("cookie delete requires url or http(s) tabId");
2120
+ const res = await chrome.cookies.remove({ url: baseUrl, name });
2121
+ return { success: Boolean(res) };
2122
+ }
2123
+
2124
+ if (action === "delete_all") {
2125
+ const dom = typeof p.domain === "string" ? p.domain.trim() : "";
2126
+ if (!dom) throw new Error("cookie delete_all requires domain");
2127
+ const normalized = dom.startsWith(".") ? dom : `.${dom}`;
2128
+ const all = await chrome.cookies.getAll({ domain: normalized });
2129
+ for (const c of all) {
2130
+ const u = cookieRemoveUrl(c);
2131
+ await chrome.cookies.remove({ url: u, name: c.name });
2132
+ }
2133
+ return { success: true, cookie: all.map(serializeCookie) };
2134
+ }
2135
+
2136
+ throw new Error("cookie_manager: unsupported action");
2137
+ }
2138
+
2139
+ /** @param {unknown} payload */
2140
+ async function handleFillForm(payload) {
2141
+ const p = asPayload(payload);
2142
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
2143
+ const fields = Array.isArray(p.fields) ? p.fields : [];
2144
+ const res = await chrome.tabs
2145
+ .sendMessage(tabId, {
2146
+ type: "POKE_FILL_FORM",
2147
+ fields,
2148
+ submitAfter: p.submitAfter === true,
2149
+ submitSelector: typeof p.submitSelector === "string" ? p.submitSelector : undefined,
2150
+ })
2151
+ .catch((e) => {
2152
+ throw new Error(`fill_form relay failed: ${String(e)}`);
2153
+ });
2154
+ return withTabMeta(tabId, res);
2155
+ }
2156
+
2157
+ /** @param {unknown} payload */
2158
+ async function handleGetStorage(payload) {
2159
+ const p = asPayload(payload);
2160
+ const type = p.type === "local" || p.type === "session" || p.type === "cookie" ? p.type : "local";
2161
+ const key = typeof p.key === "string" ? p.key : undefined;
2162
+
2163
+ if (type === "cookie") {
2164
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
2165
+ const url = await tabHttpUrl(tabId);
2166
+ const all = await chrome.cookies.getAll({ url });
2167
+ /** @type {Record<string, string>} */
2168
+ const data = {};
2169
+ for (const c of all) {
2170
+ if (key && c.name !== key) continue;
2171
+ data[c.name] = c.value;
2172
+ }
2173
+ return { data, count: Object.keys(data).length };
2174
+ }
2175
+
2176
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
2177
+ const res = await chrome.tabs
2178
+ .sendMessage(tabId, {
2179
+ type: "POKE_GET_STORAGE",
2180
+ storageType: type,
2181
+ key,
2182
+ })
2183
+ .catch((e) => {
2184
+ throw new Error(`get_storage relay failed: ${String(e)}`);
2185
+ });
2186
+ return res;
2187
+ }
2188
+
2189
+ /** @param {unknown} payload */
2190
+ async function handleSetStorage(payload) {
2191
+ const p = asPayload(payload);
2192
+ const type = p.type === "local" || p.type === "session" ? p.type : "local";
2193
+ const key = typeof p.key === "string" ? p.key : "";
2194
+ const value = typeof p.value === "string" ? p.value : "";
2195
+ if (!key) throw new Error("set_storage requires key");
2196
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
2197
+ const res = await chrome.tabs
2198
+ .sendMessage(tabId, {
2199
+ type: "POKE_SET_STORAGE",
2200
+ storageType: type,
2201
+ key,
2202
+ value,
2203
+ })
2204
+ .catch((e) => {
2205
+ throw new Error(`set_storage relay failed: ${String(e)}`);
2206
+ });
2207
+ return res;
2208
+ }
2209
+
2210
+ /** @param {unknown} payload */
2211
+ async function handleHoverElement(payload) {
2212
+ const p = asPayload(payload);
2213
+ const tabId = await resolveTabId(typeof p.tabId === "number" ? p.tabId : undefined);
2214
+ const selector = typeof p.selector === "string" ? p.selector.trim() : "";
2215
+ const x = typeof p.x === "number" ? p.x : Number(p.x);
2216
+ const y = typeof p.y === "number" ? p.y : Number(p.y);
2217
+ const hasXY = Number.isFinite(x) && Number.isFinite(y);
2218
+
2219
+ if (selector) {
2220
+ const res = await chrome.tabs.sendMessage(tabId, { type: "POKE_HOVER_ELEMENT", selector }).catch((e) => {
2221
+ throw new Error(`hover_element relay failed: ${String(e)}`);
2222
+ });
2223
+ return withTabMeta(tabId, res);
2224
+ }
2225
+ if (hasXY) {
2226
+ await showCursorFeedbackDot(tabId, x, y);
2227
+ await debuggerAttachForTool(tabId);
2228
+ try {
2229
+ await debuggerSend(tabId, "Input.dispatchMouseEvent", {
2230
+ type: "mouseMoved",
2231
+ x,
2232
+ y,
2233
+ });
2234
+ return withTabMeta(tabId, { success: true });
2235
+ } finally {
2236
+ await debuggerDetachForTool(tabId);
2237
+ }
2238
+ }
2239
+ throw new Error("hover_element requires selector or numeric x and y");
2240
+ }
2241
+
2242
+ /** @param {unknown} payload */
2243
+ async function handleNewTab(payload) {
2244
+ const p = asPayload(payload);
2245
+ const url = typeof p.url === "string" && p.url.length > 0 ? p.url : "about:blank";
2246
+ const tab = await chrome.tabs.create({ url, active: p.active !== false });
2247
+ if (tab.id == null) throw new Error("Failed to create tab");
2248
+ return { tabId: tab.id };
2249
+ }
2250
+
2251
+ /** @param {unknown} payload */
2252
+ async function handleCloseTab(payload) {
2253
+ const p = asPayload(payload);
2254
+ if (typeof p.tabId !== "number" || !Number.isFinite(p.tabId)) {
2255
+ throw new Error("close_tab requires tabId");
2256
+ }
2257
+ await chrome.tabs.get(p.tabId).catch(() => {
2258
+ throw new Error(`Tab not found: ${p.tabId}`);
2259
+ });
2260
+ await chrome.tabs.remove(p.tabId);
2261
+ return { closed: true, tabId: p.tabId };
2262
+ }
2263
+
2264
+ /** @param {unknown} payload */
2265
+ async function handleSwitchTab(payload) {
2266
+ const p = asPayload(payload);
2267
+ if (typeof p.tabId !== "number" || !Number.isFinite(p.tabId)) {
2268
+ throw new Error("switch_tab requires tabId");
2269
+ }
2270
+ const tab = await chrome.tabs.get(p.tabId).catch(() => null);
2271
+ if (!tab?.id) throw new Error(`Tab not found: ${p.tabId}`);
2272
+ await chrome.tabs.update(p.tabId, { active: true });
2273
+ if (tab.windowId != null) {
2274
+ await chrome.windows.update(tab.windowId, { focused: true });
2275
+ }
2276
+ return { tabId: p.tabId, active: true };
2277
+ }
2278
+
2279
+ /** @type {Record<string, (payload: unknown) => Promise<unknown>>} */
2280
+ const COMMAND_HANDLERS = {
2281
+ list_tabs: handleListTabs,
2282
+ get_active_tab: handleGetActiveTab,
2283
+ navigate_to: handleNavigateTo,
2284
+ click_element: handleClickElement,
2285
+ type_text: handleTypeText,
2286
+ scroll_window: handleScrollWindow,
2287
+ screenshot: handleScreenshot,
2288
+ evaluate_js: handleEvaluateJs,
2289
+ new_tab: handleNewTab,
2290
+ close_tab: handleCloseTab,
2291
+ switch_tab: handleSwitchTab,
2292
+ get_dom_snapshot: handleGetDomSnapshot,
2293
+ get_accessibility_tree: handleGetAccessibilityTree,
2294
+ find_element: handleFindElement,
2295
+ read_page: handleReadPage,
2296
+ wait_for_selector: handleWaitForSelector,
2297
+ execute_script: handleExecuteScript,
2298
+ get_console_logs: handleGetConsoleLogs,
2299
+ clear_console_logs: handleClearConsoleLogs,
2300
+ get_network_logs: handleGetNetworkLogs,
2301
+ clear_network_logs: handleClearNetworkLogs,
2302
+ start_network_capture: handleStartNetworkCapture,
2303
+ stop_network_capture: handleStopNetworkCapture,
2304
+ hover_element: handleHoverElement,
2305
+ script_inject: handleScriptInject,
2306
+ cookie_manager: handleCookieManager,
2307
+ fill_form: handleFillForm,
2308
+ get_storage: handleGetStorage,
2309
+ set_storage: handleSetStorage,
2310
+ error_reporter: handleErrorReporter,
2311
+ get_performance_metrics: handleGetPerformanceMetrics,
2312
+ full_page_capture: handleFullPageCapture,
2313
+ pdf_export: handlePdfExport,
2314
+ device_emulate: handleDeviceEmulate,
2315
+ };
2316
+
2317
+ /**
2318
+ * @param {string} command
2319
+ * @param {unknown} payload
2320
+ */
2321
+ async function dispatchCommand(command, payload) {
2322
+ const handler = COMMAND_HANDLERS[command];
2323
+ if (!handler) throw new Error(`Unknown command: ${command}`);
2324
+ return handler(payload);
2325
+ }
2326
+
2327
+ chrome.runtime.onInstalled.addListener(() => {
2328
+ chrome.storage.local.get(["wsPort", "enabled"]).then((v) => {
2329
+ const patch = {};
2330
+ if (v.wsPort == null) patch.wsPort = DEFAULT_WS_PORT;
2331
+ if (v.enabled === undefined) patch.enabled = true;
2332
+ if (Object.keys(patch).length) chrome.storage.local.set(patch);
2333
+ });
2334
+ void (async () => {
2335
+ if (await getPokeEnabled()) {
2336
+ await ensureOffscreenAndSchedule();
2337
+ } else {
2338
+ scheduleKeepAliveAlarm();
2339
+ }
2340
+ })();
2341
+ });
2342
+
2343
+ chrome.runtime.onStartup.addListener(() => {
2344
+ void (async () => {
2345
+ if (await getPokeEnabled()) {
2346
+ await ensureOffscreenAndSchedule();
2347
+ } else {
2348
+ scheduleKeepAliveAlarm();
2349
+ }
2350
+ })();
2351
+ });
2352
+
2353
+ void (async () => {
2354
+ if (await getPokeEnabled()) {
2355
+ await ensureOffscreenAndSchedule();
2356
+ } else {
2357
+ scheduleKeepAliveAlarm();
2358
+ }
2359
+ })();
2360
+
2361
+ /** @type {Record<string, (message: unknown, sendResponse: (r: unknown) => void) => boolean | void>} */
2362
+ const RUNTIME_HANDLERS = {
2363
+ POKE_GET_STATE: (message, sendResponse) => {
2364
+ void Promise.all([getWsPort(), chrome.storage.local.get("wsAuthToken")]).then(([port, st]) => {
2365
+ const tok = st && typeof st.wsAuthToken === "string" ? st.wsAuthToken : "";
2366
+ sendResponse({
2367
+ status: mcpStatus,
2368
+ port,
2369
+ log: commandLog,
2370
+ hasAuthToken: tok.length > 0,
2371
+ });
2372
+ });
2373
+ return true;
2374
+ },
2375
+ POKE_SET_TOKEN: (message, sendResponse) => {
2376
+ const m = /** @type {{ token?: unknown }} */ (message);
2377
+ const token = typeof m.token === "string" ? m.token : "";
2378
+ void chrome.storage.local.set({ wsAuthToken: token }).then(async () => {
2379
+ if (await getPokeEnabled()) {
2380
+ await ensureOffscreenAndSchedule();
2381
+ try {
2382
+ bridgePort?.postMessage({ type: "reconnect" });
2383
+ } catch {
2384
+ /* ignore */
2385
+ }
2386
+ }
2387
+ sendResponse({ ok: true });
2388
+ });
2389
+ return true;
2390
+ },
2391
+ POKE_SET_PORT: (message, sendResponse) => {
2392
+ const m = /** @type {{ port?: unknown }} */ (message);
2393
+ const next = Number(m.port);
2394
+ if (!Number.isFinite(next) || next <= 0 || next >= 65536) {
2395
+ sendResponse({ ok: false, error: "Invalid port" });
2396
+ return false;
2397
+ }
2398
+ void chrome.storage.local.set({ wsPort: next }).then(async () => {
2399
+ if (await getPokeEnabled()) {
2400
+ await ensureOffscreenAndSchedule();
2401
+ try {
2402
+ bridgePort?.postMessage({ type: "reconnect", port: next });
2403
+ } catch {
2404
+ /* ignore */
2405
+ }
2406
+ }
2407
+ sendResponse({ ok: true, port: next });
2408
+ });
2409
+ return true;
2410
+ },
2411
+ POKE_RECONNECT: (_message, sendResponse) => {
2412
+ void (async () => {
2413
+ if (await getPokeEnabled()) {
2414
+ await ensureOffscreenAndSchedule();
2415
+ try {
2416
+ bridgePort?.postMessage({ type: "reconnect" });
2417
+ } catch {
2418
+ /* ignore */
2419
+ }
2420
+ }
2421
+ sendResponse({ ok: true });
2422
+ })();
2423
+ return true;
2424
+ },
2425
+ POKE_GET_API_KEY_STATE: (_message, sendResponse) => {
2426
+ void chrome.storage.local.get("pokeApiKey").then((st) => {
2427
+ const apiKey = st && typeof st.pokeApiKey === "string" ? st.pokeApiKey.trim() : "";
2428
+ sendResponse({ hasApiKey: apiKey.length > 0 });
2429
+ });
2430
+ return true;
2431
+ },
2432
+ POKE_SET_API_KEY: (message, sendResponse) => {
2433
+ const m = /** @type {{ apiKey?: unknown }} */ (message);
2434
+ const apiKey = typeof m.apiKey === "string" ? m.apiKey.trim() : "";
2435
+ void chrome.storage.local.set({ pokeApiKey: apiKey }).then(() => {
2436
+ sendResponse({ ok: true });
2437
+ });
2438
+ return true;
2439
+ },
2440
+ POKE_SEND_MESSAGE: (message, sendResponse) => {
2441
+ const m = /** @type {{ message?: unknown }} */ (message);
2442
+ const userMessage = typeof m.message === "string" ? m.message.trim() : "";
2443
+ if (!userMessage) {
2444
+ sendResponse({ ok: false, error: "Message is required." });
2445
+ return false;
2446
+ }
2447
+ void (async () => {
2448
+ const st = await chrome.storage.local.get(["pokeApiKey"]);
2449
+ const apiKey = st && typeof st.pokeApiKey === "string" ? st.pokeApiKey.trim() : "";
2450
+ if (!apiKey) {
2451
+ sendResponse({ ok: false, error: "Missing API key. Save it in the popup first." });
2452
+ return;
2453
+ }
2454
+ try {
2455
+ const primaryPrompt = `${POKE_TERMINAL_ONLY_INSTRUCTION}${userMessage}`;
2456
+ const fallbackPrompt = `${POKE_TERMINAL_FALLBACK_INSTRUCTION}${userMessage}`;
2457
+
2458
+ // First path: localhost proxy through poke-browser Node process.
2459
+ let primary;
2460
+ try {
2461
+ primary = await postPokeMessageViaLocalProxy(apiKey, primaryPrompt);
2462
+ } catch {
2463
+ // Proxy unavailable; fallback to direct extension fetch.
2464
+ primary = await postPokeMessage(apiKey, primaryPrompt);
2465
+ }
2466
+
2467
+ if (primary.ok) {
2468
+ sendResponse({ ok: true, data: primary.data });
2469
+ return;
2470
+ }
2471
+
2472
+ // If backend fails with a 5xx, retry once with shorter strict instruction.
2473
+ if (primary.status >= 500) {
2474
+ let fallback;
2475
+ try {
2476
+ fallback = await postPokeMessageViaLocalProxy(apiKey, fallbackPrompt);
2477
+ } catch {
2478
+ fallback = await postPokeMessage(apiKey, fallbackPrompt);
2479
+ }
2480
+ if (fallback.ok) {
2481
+ sendResponse({
2482
+ ok: true,
2483
+ data: fallback.data,
2484
+ warning: `Primary prompt failed with ${primary.status}; fallback succeeded.`,
2485
+ });
2486
+ return;
2487
+ }
2488
+ sendResponse({
2489
+ ok: false,
2490
+ error:
2491
+ `Poke API error (${fallback.status}). ` +
2492
+ (fallback.serverMsg || fallback.statusText || "Unknown server error."),
2493
+ });
2494
+ return;
2495
+ }
2496
+
2497
+ sendResponse({
2498
+ ok: false,
2499
+ error:
2500
+ `Poke API error (${primary.status}). ` +
2501
+ (primary.serverMsg || primary.statusText || "Unknown server error."),
2502
+ });
2503
+ } catch (err) {
2504
+ sendResponse({
2505
+ ok: false,
2506
+ error: err instanceof Error ? err.message : String(err),
2507
+ });
2508
+ }
2509
+ })();
2510
+ return true;
2511
+ },
2512
+ };
2513
+
2514
+ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
2515
+ if (message && typeof message === "object" && message.action === "reconnect") {
2516
+ const wsUrl =
2517
+ typeof message.wsUrl === "string" && message.wsUrl.trim() ? message.wsUrl.trim() : "";
2518
+ void (async () => {
2519
+ if (wsUrl) {
2520
+ await chrome.storage.local.set({ wsUrl });
2521
+ }
2522
+ if (!(await getPokeEnabled())) {
2523
+ sendResponse({ ok: true });
2524
+ return;
2525
+ }
2526
+ await ensureOffscreenAndSchedule();
2527
+ try {
2528
+ bridgePort?.postMessage(wsUrl ? { type: "reconnect", wsUrl } : { type: "reconnect" });
2529
+ } catch {
2530
+ /* ignore */
2531
+ }
2532
+ sendResponse({ ok: true });
2533
+ })();
2534
+ return true;
2535
+ }
2536
+ if (message && typeof message === "object" && message.action === "setPokeBrowserEnabled") {
2537
+ const enabled = message.enabled === true;
2538
+ void (async () => {
2539
+ await chrome.storage.local.set({ enabled });
2540
+ if (enabled) {
2541
+ await ensureOffscreenAndSchedule();
2542
+ } else {
2543
+ await stopMcpConnection();
2544
+ }
2545
+ sendResponse({ ok: true });
2546
+ })();
2547
+ return true;
2548
+ }
2549
+ const t = message && typeof message === "object" && "type" in message ? String(message.type) : "";
2550
+ const fn = RUNTIME_HANDLERS[t];
2551
+ if (fn) return fn(message, sendResponse);
2552
+ return undefined;
2553
+ });