Compare commits

...

6 Commits

Author SHA1 Message Date
Samuel Attard
77189f7a39 chore: fix lint 2020-11-10 14:07:04 -08:00
Samuel Attard
28b794216d fixup tests 2020-11-10 14:03:02 -08:00
Samuel Attard
58d9c451c6 chore: fixup linting 2020-11-10 14:03:00 -08:00
Samuel Attard
71d8a4a466 chore: fixup docs 2020-11-10 14:02:22 -08:00
Samuel Attard
825f3a3a7d chore: fixup for review 2020-11-10 14:02:22 -08:00
Samuel Attard
b2625db2ba feat: add support for will-navigate and will-fail-load custom error pages 2020-11-10 14:02:20 -08:00
14 changed files with 190 additions and 36 deletions

View File

@@ -0,0 +1,6 @@
# CancellableNavigationEvent Object extends `Event`
* `returnValue` Object - Set this to cancel the navigation event and optionally return a custom error code or error page
* `errorCode` Number - Can be any error code from the [Net Error List](https://source.chromium.org/chromium/chromium/src/+/master:net/base/net_error_list.h). If you provide `errorPage` then this code can not be `-3`. We recommend using `-2` which is `net::Aborted`.
* `errorPage` String (optional) - Custom HTML error page to display when the navigation is cancelled.

View File

@@ -57,6 +57,25 @@ Process: [Main](../glossary.md#main-process)
Emitted when the navigation is done, i.e. the spinner of the tab has stopped Emitted when the navigation is done, i.e. the spinner of the tab has stopped
spinning, and the `onload` event was dispatched. spinning, and the `onload` event was dispatched.
#### Event: 'will-fail-load' _Experimental_
Returns:
* `event` [CancellableNavigationEvent](structures/cancellable-navigation-event.md)
* `url` String
* `isInPlace` Boolean
* `isMainFrame` Boolean
* `frameProcessId` Integer
* `frameRoutingId` Integer
* `errorCode` Integer
* `errorDescription` String
This event will be emitted after `did-start-loading` and always before the
`did-fail-load` event for the same navigation.
Settings `event.returnValue` to the appropriate object will result in a custom error page being
displayed using custom HTML.
#### Event: 'did-fail-load' #### Event: 'did-fail-load'
Returns: Returns:
@@ -224,11 +243,11 @@ Not emitted if the creation of the window is canceled from
See [`window.open()`](window-open.md) for more details and how to use this in conjunction with `webContents.setWindowOpenHandler`. See [`window.open()`](window-open.md) for more details and how to use this in conjunction with `webContents.setWindowOpenHandler`.
#### Event: 'will-navigate' #### Event: 'will-navigate' _Experimental_
Returns: Returns:
* `event` Event * `event` [CancellableNavigationEvent](structures/cancellable-navigation-event.md)
* `url` String * `url` String
Emitted when a user or the page wants to start navigation. It can happen when Emitted when a user or the page wants to start navigation. It can happen when
@@ -243,6 +262,9 @@ this purpose.
Calling `event.preventDefault()` will prevent the navigation. Calling `event.preventDefault()` will prevent the navigation.
Settings `event.returnValue` to the appropriate object will result in a custom error page being
displayed using custom HTML and the navigation being cancelled.
#### Event: 'did-start-navigation' #### Event: 'did-start-navigation'
Returns: Returns:

View File

@@ -819,7 +819,7 @@ which potential security issues are not as widely known.
[webview-tag]: ../api/webview-tag.md [webview-tag]: ../api/webview-tag.md
[web-contents]: ../api/web-contents.md [web-contents]: ../api/web-contents.md
[window-open-handler]: ../api/web-contents.md#contentssetwindowopenhandler-handler [window-open-handler]: ../api/web-contents.md#contentssetwindowopenhandler-handler
[will-navigate]: ../api/web-contents.md#event-will-navigate [will-navigate]: ../api/web-contents.md#event-will-navigateexperimental
[open-external]: ../api/shell.md#shellopenexternalurl-options [open-external]: ../api/shell.md#shellopenexternalurl-options
[sandbox]: ../api/sandbox-option.md [sandbox]: ../api/sandbox-option.md
[responsible-disclosure]: https://en.wikipedia.org/wiki/Responsible_disclosure [responsible-disclosure]: https://en.wikipedia.org/wiki/Responsible_disclosure

View File

@@ -73,6 +73,7 @@ auto_filenames = {
"docs/api/webview-tag.md", "docs/api/webview-tag.md",
"docs/api/window-open.md", "docs/api/window-open.md",
"docs/api/structures/bluetooth-device.md", "docs/api/structures/bluetooth-device.md",
"docs/api/structures/cancellable-navigation-event.md",
"docs/api/structures/certificate-principal.md", "docs/api/structures/certificate-principal.md",
"docs/api/structures/certificate.md", "docs/api/structures/certificate.md",
"docs/api/structures/cookie.md", "docs/api/structures/cookie.md",

View File

@@ -574,6 +574,31 @@ WebContents.prototype._init = function () {
ipcMain.emit(channel, event, message); ipcMain.emit(channel, event, message);
}); });
const handleCustomErrorPageEvent = (eventName: string) => {
this.on(`-${eventName}` as any, function (this: any, event: any, ...args: any[]) {
let allowReturnValue = true;
Object.defineProperty(event, 'returnValue', {
set: (value) => {
if (!allowReturnValue) return;
if (typeof value !== 'object' || !value) { throw new TypeError(`event.returnValue must be set to a non-null object, was set to a "${typeof value}"`); }
if (typeof value.errorCode !== 'number') { throw new TypeError(`event.returnValue.errorCode must be set to a number, was set to a "${typeof value.errorCode}"`); }
if (value.errorCode >= 0) { throw new TypeError(`event.returnValue.errorCode must be negative, was set to "${value.errorCode}"`); }
if (value.errorPage && typeof value.errorPage !== 'string') { throw new TypeError(`event.returnValue.errorPage must be set to a string if provided, was set to a "${typeof value.errorPage}"`); }
if (typeof value.errorPage === 'string' && value.errorPage.length === 0) { throw new Error('event.returnValue.errorPage must be a non-empty string, an empty string was provided'); }
if (value.errorPage && value.errorCode === -3) { throw new Error('event.returnValue.errorCode can not be set to "-2" when an errorPage is provided'); }
event.preventDefault();
event.sendReply(value);
},
get: () => {}
});
this.emit(eventName, event, ...args);
allowReturnValue = false;
});
};
handleCustomErrorPageEvent('will-navigate');
handleCustomErrorPageEvent('will-fail-load');
// Handle context menu action request from pepper plugin. // Handle context menu action request from pepper plugin.
this.on('pepper-context-menu' as any, function (event: any, params: {x: number, y: number, menu: Array<(MenuItemConstructorOptions) | (MenuItem)>}, callback: () => void) { this.on('pepper-context-menu' as any, function (event: any, params: {x: number, y: number, menu: Array<(MenuItemConstructorOptions) | (MenuItem)>}, callback: () => void) {
// Access Menu via electron.Menu to prevent circular require. // Access Menu via electron.Menu to prevent circular require.

View File

@@ -1470,7 +1470,8 @@ void WebContents::DidStopLoading() {
bool WebContents::EmitNavigationEvent( bool WebContents::EmitNavigationEvent(
const std::string& event, const std::string& event,
content::NavigationHandle* navigation_handle) { content::NavigationHandle* navigation_handle,
gin_helper::Event::ValueCallback callback) {
bool is_main_frame = navigation_handle->IsInMainFrame(); bool is_main_frame = navigation_handle->IsInMainFrame();
int frame_tree_node_id = navigation_handle->GetFrameTreeNodeId(); int frame_tree_node_id = navigation_handle->GetFrameTreeNodeId();
content::FrameTreeNode* frame_tree_node = content::FrameTreeNode* frame_tree_node =
@@ -1490,8 +1491,18 @@ bool WebContents::EmitNavigationEvent(
} }
bool is_same_document = navigation_handle->IsSameDocument(); bool is_same_document = navigation_handle->IsSameDocument();
auto url = navigation_handle->GetURL(); auto url = navigation_handle->GetURL();
return Emit(event, url, is_same_document, is_main_frame, frame_process_id, int code = navigation_handle->GetNetErrorCode();
frame_routing_id); auto description = net::ErrorToShortString(code);
return EmitWithSender(event, nullptr, std::move(callback), url,
is_same_document, is_main_frame, frame_process_id,
frame_routing_id, code, description);
}
bool WebContents::EmitNavigationEvent(
const std::string& event,
content::NavigationHandle* navigation_handle) {
return EmitNavigationEvent(event, navigation_handle,
gin_helper::Event::ValueCallback());
} }
void WebContents::BindElectronBrowser( void WebContents::BindElectronBrowser(
@@ -1513,8 +1524,9 @@ void WebContents::Message(bool internal,
TRACE_EVENT1("electron", "WebContents::Message", "channel", channel); TRACE_EVENT1("electron", "WebContents::Message", "channel", channel);
// webContents.emit('-ipc-message', new Event(), internal, channel, // webContents.emit('-ipc-message', new Event(), internal, channel,
// arguments); // arguments);
EmitWithSender("-ipc-message", receivers_.current_context(), InvokeCallback(), EmitWithSender("-ipc-message", receivers_.current_context(),
internal, channel, std::move(arguments)); gin_helper::Event::ValueCallback(), internal, channel,
std::move(arguments));
} }
void WebContents::Invoke(bool internal, void WebContents::Invoke(bool internal,
@@ -1524,7 +1536,9 @@ void WebContents::Invoke(bool internal,
TRACE_EVENT1("electron", "WebContents::Invoke", "channel", channel); TRACE_EVENT1("electron", "WebContents::Invoke", "channel", channel);
// webContents.emit('-ipc-invoke', new Event(), internal, channel, arguments); // webContents.emit('-ipc-invoke', new Event(), internal, channel, arguments);
EmitWithSender("-ipc-invoke", receivers_.current_context(), EmitWithSender("-ipc-invoke", receivers_.current_context(),
std::move(callback), internal, channel, std::move(arguments)); gin_helper::Event::AdaptInvokeCallbackToValueCallback(
std::move(callback)),
internal, channel, std::move(arguments));
} }
void WebContents::OnFirstNonEmptyLayout() { void WebContents::OnFirstNonEmptyLayout() {
@@ -1541,8 +1555,9 @@ void WebContents::ReceivePostMessage(const std::string& channel,
MessagePort::EntanglePorts(isolate, std::move(message.ports)); MessagePort::EntanglePorts(isolate, std::move(message.ports));
v8::Local<v8::Value> message_value = v8::Local<v8::Value> message_value =
electron::DeserializeV8Value(isolate, message); electron::DeserializeV8Value(isolate, message);
EmitWithSender("-ipc-ports", receivers_.current_context(), InvokeCallback(), EmitWithSender("-ipc-ports", receivers_.current_context(),
false, channel, message_value, std::move(wrapped_ports)); gin_helper::Event::ValueCallback(), false, channel,
message_value, std::move(wrapped_ports));
} }
void WebContents::PostMessage(const std::string& channel, void WebContents::PostMessage(const std::string& channel,
@@ -1586,7 +1601,9 @@ void WebContents::MessageSync(bool internal,
// webContents.emit('-ipc-message-sync', new Event(sender, message), internal, // webContents.emit('-ipc-message-sync', new Event(sender, message), internal,
// channel, arguments); // channel, arguments);
EmitWithSender("-ipc-message-sync", receivers_.current_context(), EmitWithSender("-ipc-message-sync", receivers_.current_context(),
std::move(callback), internal, channel, std::move(arguments)); gin_helper::Event::AdaptInvokeCallbackToValueCallback(
std::move(callback)),
internal, channel, std::move(arguments));
} }
void WebContents::MessageTo(bool internal, void WebContents::MessageTo(bool internal,
@@ -1608,7 +1625,8 @@ void WebContents::MessageHost(const std::string& channel,
TRACE_EVENT1("electron", "WebContents::MessageHost", "channel", channel); TRACE_EVENT1("electron", "WebContents::MessageHost", "channel", channel);
// webContents.emit('ipc-message-host', new Event(), channel, args); // webContents.emit('ipc-message-host', new Event(), channel, args);
EmitWithSender("ipc-message-host", receivers_.current_context(), EmitWithSender("ipc-message-host", receivers_.current_context(),
InvokeCallback(), channel, std::move(arguments)); gin_helper::Event::ValueCallback(), channel,
std::move(arguments));
} }
void WebContents::UpdateDraggableRegions( void WebContents::UpdateDraggableRegions(

View File

@@ -30,6 +30,7 @@
#include "mojo/public/cpp/bindings/receiver_set.h" #include "mojo/public/cpp/bindings/receiver_set.h"
#include "printing/buildflags/buildflags.h" #include "printing/buildflags/buildflags.h"
#include "services/service_manager/public/cpp/binder_registry.h" #include "services/service_manager/public/cpp/binder_registry.h"
#include "shell/browser/api/event.h"
#include "shell/browser/api/frame_subscriber.h" #include "shell/browser/api/frame_subscriber.h"
#include "shell/browser/api/save_page_handler.h" #include "shell/browser/api/save_page_handler.h"
#include "shell/browser/event_emitter_mixin.h" #include "shell/browser/event_emitter_mixin.h"
@@ -408,11 +409,15 @@ class WebContents : public gin::Wrappable<WebContents>,
bool EmitNavigationEvent(const std::string& event, bool EmitNavigationEvent(const std::string& event,
content::NavigationHandle* navigation_handle); content::NavigationHandle* navigation_handle);
bool EmitNavigationEvent(const std::string& event,
content::NavigationHandle* navigation_handle,
gin_helper::Event::ValueCallback callback);
// this.emit(name, new Event(sender, message), args...); // this.emit(name, new Event(sender, message), args...);
template <typename... Args> template <typename... Args>
bool EmitWithSender(base::StringPiece name, bool EmitWithSender(base::StringPiece name,
content::RenderFrameHost* sender, content::RenderFrameHost* sender,
electron::mojom::ElectronBrowser::InvokeCallback callback, gin_helper::Event::ValueCallback callback,
Args&&... args) { Args&&... args) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();

View File

@@ -14,6 +14,22 @@
namespace gin_helper { namespace gin_helper {
namespace {
bool InvokeCallbackAdapter(Event::InvokeCallback callback,
v8::Local<v8::Value> result) {
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
blink::CloneableMessage message;
if (!gin::ConvertFromV8(isolate, result, &message)) {
return false;
}
std::move(callback).Run(std::move(message));
return true;
}
} // namespace
gin::WrapperInfo Event::kWrapperInfo = {gin::kEmbedderNativeGin}; gin::WrapperInfo Event::kWrapperInfo = {gin::kEmbedderNativeGin};
Event::Event() {} Event::Event() {}
@@ -29,7 +45,7 @@ Event::~Event() {
} }
} }
void Event::SetCallback(InvokeCallback callback) { void Event::SetCallback(ValueCallback callback) {
DCHECK(!callback_); DCHECK(!callback_);
callback_ = std::move(callback); callback_ = std::move(callback);
} }
@@ -45,13 +61,7 @@ bool Event::SendReply(v8::Isolate* isolate, v8::Local<v8::Value> result) {
if (!callback_) if (!callback_)
return false; return false;
blink::CloneableMessage message; return std::move(callback_).Run(result);
if (!gin::ConvertFromV8(isolate, result, &message)) {
return false;
}
std::move(callback_).Run(std::move(message));
return true;
} }
gin::ObjectTemplateBuilder Event::GetObjectTemplateBuilder( gin::ObjectTemplateBuilder Event::GetObjectTemplateBuilder(
@@ -70,4 +80,12 @@ gin::Handle<Event> Event::Create(v8::Isolate* isolate) {
return gin::CreateHandle(isolate, new Event()); return gin::CreateHandle(isolate, new Event());
} }
Event::ValueCallback Event::AdaptInvokeCallbackToValueCallback(
Event::InvokeCallback callback) {
if (!callback)
return ValueCallback();
return base::BindOnce(InvokeCallbackAdapter, std::move(callback));
}
} // namespace gin_helper } // namespace gin_helper

View File

@@ -18,17 +18,21 @@ namespace gin_helper {
class Event : public gin::Wrappable<Event> { class Event : public gin::Wrappable<Event> {
public: public:
using InvokeCallback = electron::mojom::ElectronBrowser::InvokeCallback; using InvokeCallback = electron::mojom::ElectronBrowser::InvokeCallback;
using ValueCallback = base::OnceCallback<bool(v8::Local<v8::Value>)>;
static gin::WrapperInfo kWrapperInfo; static gin::WrapperInfo kWrapperInfo;
static gin::Handle<Event> Create(v8::Isolate* isolate); static gin::Handle<Event> Create(v8::Isolate* isolate);
// Pass the callback to be invoked. static ValueCallback AdaptInvokeCallbackToValueCallback(
void SetCallback(InvokeCallback callback); InvokeCallback callback);
// event.PreventDefault(). // event.PreventDefault().
void PreventDefault(v8::Isolate* isolate); void PreventDefault(v8::Isolate* isolate);
// Pass the callback to be invoked.
void SetCallback(ValueCallback callback);
// event.sendReply(value), used for replying to synchronous messages and // event.sendReply(value), used for replying to synchronous messages and
// `invoke` calls. // `invoke` calls.
bool SendReply(v8::Isolate* isolate, v8::Local<v8::Value> result); bool SendReply(v8::Isolate* isolate, v8::Local<v8::Value> result);
@@ -44,7 +48,7 @@ class Event : public gin::Wrappable<Event> {
private: private:
// Replyer for the synchronous messages. // Replyer for the synchronous messages.
InvokeCallback callback_; ValueCallback callback_;
DISALLOW_COPY_AND_ASSIGN(Event); DISALLOW_COPY_AND_ASSIGN(Event);
}; };

View File

@@ -4,12 +4,32 @@
#include "shell/browser/electron_navigation_throttle.h" #include "shell/browser/electron_navigation_throttle.h"
#include <memory>
#include <string>
#include "content/public/browser/navigation_handle.h" #include "content/public/browser/navigation_handle.h"
#include "shell/browser/api/electron_api_web_contents.h" #include "shell/browser/api/electron_api_web_contents.h"
#include "shell/browser/javascript_environment.h" #include "shell/browser/javascript_environment.h"
#include "shell/common/gin_helper/dictionary.h"
namespace electron { namespace electron {
namespace {
bool HandleEventReturnValue(int* error_code,
std::string* error_html,
v8::Local<v8::Value> val) {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
gin_helper::Dictionary dict;
gin::ConvertFromV8(isolate, val, &dict);
if (!dict.Get("errorCode", error_code))
return false;
dict.Get("errorPage", error_html);
return true;
}
} // namespace
ElectronNavigationThrottle::ElectronNavigationThrottle( ElectronNavigationThrottle::ElectronNavigationThrottle(
content::NavigationHandle* navigation_handle) content::NavigationHandle* navigation_handle)
: content::NavigationThrottle(navigation_handle) {} : content::NavigationThrottle(navigation_handle) {}
@@ -21,7 +41,9 @@ const char* ElectronNavigationThrottle::GetNameForLogging() {
} }
content::NavigationThrottle::ThrottleCheckResult content::NavigationThrottle::ThrottleCheckResult
ElectronNavigationThrottle::WillStartRequest() { ElectronNavigationThrottle::DelegateEventToWebContents(
const std::string& event_name,
net::Error error_code) {
auto* handle = navigation_handle(); auto* handle = navigation_handle();
auto* contents = handle->GetWebContents(); auto* contents = handle->GetWebContents();
if (!contents) { if (!contents) {
@@ -37,13 +59,37 @@ ElectronNavigationThrottle::WillStartRequest() {
return PROCEED; return PROCEED;
} }
if (handle->IsRendererInitiated() && handle->IsInMainFrame() && int error_code_int = error_code;
api_contents->EmitNavigationEvent("will-navigate", handle)) { std::string error_html;
return CANCEL; // JS ensures that this callback can not be called out-of-stack
if (api_contents->EmitNavigationEvent(
event_name, handle,
base::BindOnce(&HandleEventReturnValue,
base::Unretained(&error_code_int),
base::Unretained(&error_html)))) {
if (!error_html.empty()) {
return content::NavigationThrottle::ThrottleCheckResult(
CANCEL, error_code, error_html);
}
return content::NavigationThrottle::ThrottleCheckResult(CANCEL, error_code);
} }
return PROCEED; return PROCEED;
} }
content::NavigationThrottle::ThrottleCheckResult
ElectronNavigationThrottle::WillFailRequest() {
return DelegateEventToWebContents("-will-fail-load",
navigation_handle()->GetNetErrorCode());
}
content::NavigationThrottle::ThrottleCheckResult
ElectronNavigationThrottle::WillStartRequest() {
auto* handle = navigation_handle();
if (handle->IsRendererInitiated() && handle->IsInMainFrame())
return DelegateEventToWebContents("-will-navigate", net::ERR_ABORTED);
return PROCEED;
}
content::NavigationThrottle::ThrottleCheckResult content::NavigationThrottle::ThrottleCheckResult
ElectronNavigationThrottle::WillRedirectRequest() { ElectronNavigationThrottle::WillRedirectRequest() {
auto* handle = navigation_handle(); auto* handle = navigation_handle();

View File

@@ -5,6 +5,8 @@
#ifndef SHELL_BROWSER_ELECTRON_NAVIGATION_THROTTLE_H_ #ifndef SHELL_BROWSER_ELECTRON_NAVIGATION_THROTTLE_H_
#define SHELL_BROWSER_ELECTRON_NAVIGATION_THROTTLE_H_ #define SHELL_BROWSER_ELECTRON_NAVIGATION_THROTTLE_H_
#include <string>
#include "content/public/browser/navigation_throttle.h" #include "content/public/browser/navigation_throttle.h"
namespace electron { namespace electron {
@@ -16,12 +18,18 @@ class ElectronNavigationThrottle : public content::NavigationThrottle {
ElectronNavigationThrottle::ThrottleCheckResult WillStartRequest() override; ElectronNavigationThrottle::ThrottleCheckResult WillStartRequest() override;
ElectronNavigationThrottle::ThrottleCheckResult WillFailRequest() override;
ElectronNavigationThrottle::ThrottleCheckResult WillRedirectRequest() ElectronNavigationThrottle::ThrottleCheckResult WillRedirectRequest()
override; override;
const char* GetNameForLogging() override; const char* GetNameForLogging() override;
private: private:
content::NavigationThrottle::ThrottleCheckResult DelegateEventToWebContents(
const std::string& event_name,
net::Error error_code);
DISALLOW_COPY_AND_ASSIGN(ElectronNavigationThrottle); DISALLOW_COPY_AND_ASSIGN(ElectronNavigationThrottle);
}; };

View File

@@ -49,13 +49,12 @@ v8::Local<v8::Object> CreateEvent(v8::Isolate* isolate,
return event; return event;
} }
v8::Local<v8::Object> CreateNativeEvent( v8::Local<v8::Object> CreateNativeEvent(v8::Isolate* isolate,
v8::Isolate* isolate, v8::Local<v8::Object> sender,
v8::Local<v8::Object> sender, content::RenderFrameHost* frame,
content::RenderFrameHost* frame, Event::ValueCallback callback) {
electron::mojom::ElectronBrowser::MessageSyncCallback callback) {
v8::Local<v8::Object> event; v8::Local<v8::Object> event;
if (frame && callback) { if (callback) {
gin::Handle<Event> native_event = Event::Create(isolate); gin::Handle<Event> native_event = Event::Create(isolate);
native_event->SetCallback(std::move(callback)); native_event->SetCallback(std::move(callback));
event = v8::Local<v8::Object>::Cast(native_event.ToV8()); event = v8::Local<v8::Object>::Cast(native_event.ToV8());

View File

@@ -29,7 +29,7 @@ v8::Local<v8::Object> CreateNativeEvent(
v8::Isolate* isolate, v8::Isolate* isolate,
v8::Local<v8::Object> sender, v8::Local<v8::Object> sender,
content::RenderFrameHost* frame, content::RenderFrameHost* frame,
electron::mojom::ElectronBrowser::MessageSyncCallback callback); base::OnceCallback<bool(v8::Local<v8::Value>)> callback);
} // namespace internal } // namespace internal

View File

@@ -325,6 +325,8 @@
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/@types/klaw/-/klaw-3.0.1.tgz#29f90021c0234976aa4eb97efced9cb6db9fa8b3" resolved "https://registry.yarnpkg.com/@types/klaw/-/klaw-3.0.1.tgz#29f90021c0234976aa4eb97efced9cb6db9fa8b3"
integrity sha512-acnF3n9mYOr1aFJKFyvfNX0am9EtPUsYPq22QUCGdJE+MVt6UyAN1jwo+PmOPqXD4K7ZS9MtxDEp/un0lxFccA== integrity sha512-acnF3n9mYOr1aFJKFyvfNX0am9EtPUsYPq22QUCGdJE+MVt6UyAN1jwo+PmOPqXD4K7ZS9MtxDEp/un0lxFccA==
dependencies:
"@types/node" "*"
"@types/linkify-it@*": "@types/linkify-it@*":
version "2.1.0" version "2.1.0"