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.
- package/extension/background.js +2553 -1
- package/extension/content.js +1484 -1
- package/extension/manifest.json +1 -1
- package/package.json +2 -1
package/extension/background.js
CHANGED
|
@@ -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
|
+
});
|