Implementation for electronjs cookie handling

This commit is contained in:
Frederik Vermeersch
2020-05-22 16:38:34 +02:00
parent 6d0fbde5ad
commit d409c3a0f9
9 changed files with 413 additions and 1 deletions

154
ElectronNET.API/Cookies.cs Normal file
View File

@@ -0,0 +1,154 @@
using System;
using System.Threading.Tasks;
using ElectronNET.API.Entities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
namespace ElectronNET.API
{
/// <summary>
/// Query and modify a session's cookies.
/// </summary>
public class Cookies
{
/// <summary>
/// Gets the identifier.
/// </summary>
/// <value>
/// The identifier.
/// </value>
public int Id { get; private set; }
internal Cookies(int id)
{
Id = id;
}
/// <summary>
/// Emitted when a cookie is changed because it was added, edited, removed, or expired.
/// </summary>
public event Action<Cookie, CookieChangedCause, bool> OnChanged
{
add
{
if (_changed == null)
{
BridgeConnector.Socket.On("webContents-session-cookies-changed" + Id, (args) =>
{
Cookie cookie = ((JArray)args)[0].ToObject<Cookie>();
CookieChangedCause cause = ((JArray)args)[1].ToObject<CookieChangedCause>();
bool removed = ((JArray)args)[2].ToObject<bool>();
_changed(cookie, cause, removed);
});
BridgeConnector.Socket.Emit("register-webContents-session-cookies-changed", Id);
}
_changed += value;
}
remove
{
_changed -= value;
if (_changed == null)
BridgeConnector.Socket.Off("webContents-session-cookies-changed" + Id);
}
}
private event Action<Cookie, CookieChangedCause, bool> _changed;
/// <summary>
/// Sends a request to get all cookies matching filter, and resolves a callack with the response.
/// </summary>
/// <param name="filter">
/// </param>
/// <returns>A task which resolves an array of cookie objects.</returns>
public Task<Cookie[]> GetAsync(CookieFilter filter)
{
var taskCompletionSource = new TaskCompletionSource<Cookie[]>();
string guid = Guid.NewGuid().ToString();
BridgeConnector.Socket.On("webContents-session-cookies-get-completed" + guid, (cookies) =>
{
Cookie[] result = ((JArray)cookies).ToObject<Cookie[]>();
BridgeConnector.Socket.Off("webContents-session-cookies-get-completed" + guid);
taskCompletionSource.SetResult(result);
});
BridgeConnector.Socket.Emit("webContents-session-cookies-get", Id, JObject.FromObject(filter, _jsonSerializer), guid);
return taskCompletionSource.Task;
}
/// <summary>
///
/// </summary>
/// <param name="details"></param>
/// <returns></returns>
public Task SetAsync(CookieDetails details)
{
var taskCompletionSource = new TaskCompletionSource<object>();
string guid = Guid.NewGuid().ToString();
BridgeConnector.Socket.On("webContents-session-cookies-set-completed" + guid, () =>
{
BridgeConnector.Socket.Off("webContents-session-cookies-set-completed" + guid);
taskCompletionSource.SetResult(null);
});
BridgeConnector.Socket.Emit("webContents-session-cookies-set", Id, JObject.FromObject(details, _jsonSerializer), guid);
return taskCompletionSource.Task;
}
/// <summary>
/// Removes the cookies matching url and name
/// </summary>
/// <param name="url">The URL associated with the cookie.</param>
/// <param name="name">The name of cookie to remove.</param>
/// <returns>A task which resolves when the cookie has been removed</returns>
public Task RemoveAsync(string url, string name)
{
var taskCompletionSource = new TaskCompletionSource<object>();
string guid = Guid.NewGuid().ToString();
BridgeConnector.Socket.On("webContents-session-cookies-remove-completed" + guid, () =>
{
BridgeConnector.Socket.Off("webContents-session-cookies-remove-completed" + guid);
taskCompletionSource.SetResult(null);
});
BridgeConnector.Socket.Emit("webContents-session-cookies-remove", Id, url, name, guid);
return taskCompletionSource.Task;
}
/// <summary>
/// Writes any unwritten cookies data to disk.
/// </summary>
/// <returns>A task which resolves when the cookie store has been flushed</returns>
public Task FlushStoreAsync()
{
var taskCompletionSource = new TaskCompletionSource<object>();
string guid = Guid.NewGuid().ToString();
BridgeConnector.Socket.On("webContents-session-cookies-flushStore-completed" + guid, () =>
{
BridgeConnector.Socket.Off("webContents-session-cookies-flushStore-completed" + guid);
taskCompletionSource.SetResult(null);
});
BridgeConnector.Socket.Emit("webContents-session-cookies-flushStore", Id, guid);
return taskCompletionSource.Task;
}
private JsonSerializer _jsonSerializer = new JsonSerializer()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore
};
}
}

View File

@@ -0,0 +1,51 @@
namespace ElectronNET.API.Entities {
/// <summary>
///
/// </summary>
public class Cookie {
/// <summary>
/// The name of the cookie.
/// </summary>
public string Name { get; set;}
/// <summary>
/// The value of the cookie.
/// </summary>
public string Value { get; set; }
/// <summary>
/// (optional) - The domain of the cookie; this will be normalized with a preceding dot so that it's also valid for subdomains.
/// </summary>
public string Domain { get; set; }
/// <summary>
/// (optional) - Whether the cookie is a host-only cookie; this will only be true if no domain was passed.
/// </summary>
public bool HostOnly { get; set; }
/// <summary>
/// (optional) - The path of the cookie.
/// </summary>
public string Path { get; set; }
/// <summary>
/// (optional) - Whether the cookie is marked as secure.
/// </summary>
public bool Secure { get; set; }
/// <summary>
/// (optional) - Whether the cookie is marked as HTTP only.
/// </summary>
public bool HttpOnly { get; set; }
/// <summary>
/// (optional) - Whether the cookie is a session cookie or a persistent cookie with an expiration date.
/// </summary>
public bool Session { get; set; }
/// <summary>
/// (optional) - The expiration date of the cookie as the number of seconds since the UNIX epoch. Not provided for session cookies.
/// </summary>
public long ExpirationDate { get; set; }
}
}

View File

@@ -0,0 +1,38 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace ElectronNET.API.Entities {
/// <summary>
/// The cause of the change
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum CookieChangedCause
{
/// <summary>
///The cookie was changed directly by a consumer's action.
/// </summary>
[JsonProperty("explicit")]
@explicit,
/// <summary>
/// The cookie was automatically removed due to an insert operation that overwrote it.
/// </summary>
overwrite,
/// <summary>
/// The cookie was automatically removed as it expired.
/// </summary>
expired,
/// <summary>
/// The cookie was automatically evicted during garbage collection.
/// </summary>
evicted,
/// <summary>
/// The cookie was overwritten with an already-expired expiration date.
/// </summary>
[JsonProperty("expired_overwrite")]
expiredOverwrite
}
}

View File

@@ -0,0 +1,56 @@
using System.ComponentModel;
namespace ElectronNET.API.Entities {
/// <summary>
///
/// </summary>
public class CookieDetails {
/// <summary>
/// The URL to associate the cookie with. The callback will be rejected if the URL is invalid.
/// </summary>
public string Url { get; set; }
/// <summary>
/// (optional) - The name of the cookie. Empty by default if omitted.
/// </summary>
[DefaultValue("")]
public string Name { get; set; }
/// <summary>
/// (optional) - The value of the cookie. Empty by default if omitted.
/// </summary>
[DefaultValue("")]
public string Value { get; set; }
/// <summary>
/// (optional) - The domain of the cookie; this will be normalized with a preceding dot so that it's also valid for subdomains. Empty by default if omitted.
/// </summary>
[DefaultValue("")]
public string Domain { get; set; }
/// <summary>
/// (optional) - The path of the cookie. Empty by default if omitted.
/// </summary>
[DefaultValue("")]
public string Path { get; set; }
/// <summary>
/// (optional) - Whether the cookie is marked as secure. Defaults to false.
/// </summary>
[DefaultValue(false)]
public bool Secure { get; set; }
/// <summary>
/// (optional) - Whether the cookie is marked as HTTP only. Defaults to false.
/// </summary>
[DefaultValue(false)]
public bool HttpOnly { get; set; }
/// <summary>
/// (optional) - The expiration date of the cookie as the number of seconds since the UNIX epoch.
/// If omitted then the cookie becomes a session cookie and will not be retained between sessions.
/// </summary>
[DefaultValue(0)]
public long ExpirationDate { get; set; }
}
}

View File

@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace ElectronNET.API.Entities
{
/// <summary>
///
/// </summary>
public class CookieFilter
{
/// <summary>
/// (optional) - Retrieves cookies which are associated with url.Empty implies retrieving cookies of all URLs.
/// </summary>
public string Url { get; set; }
/// <summary>
/// (optional) - Filters cookies by name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// (optional) - Retrieves cookies whose domains match or are subdomains of domains.
/// </summary>
public string Domain { get; set; }
/// <summary>
/// (optional) - Retrieves cookies whose path matches path.
/// </summary>
public string Path { get; set; }
/// <summary>
/// (optional) - Filters cookies by their Secure property.
/// </summary>
public bool Secure { get; set; }
/// <summary>
/// (optional) - Filters out session or persistent cookies.
/// </summary>
public bool Session { get; set; }
}
}

View File

@@ -20,9 +20,15 @@ namespace ElectronNET.API
/// </value>
public int Id { get; private set; }
/// <summary>
/// Query and modify a session's cookies.
/// </summary>
public Cookies Cookies { get; }
internal Session(int id)
{
Id = id;
Cookies = new Cookies(id);
}
/// <summary>

View File

@@ -136,6 +136,33 @@ module.exports = (socket) => {
const browserWindow = getWindowById(id);
browserWindow.webContents.session.setUserAgent(userAgent, acceptLanguages);
});
socket.on('register-webContents-session-cookies-changed', (id) => {
const browserWindow = getWindowById(id);
browserWindow.webContents.session.cookies.removeAllListeners('changed');
browserWindow.webContents.session.cookies.on('changed', (event, cookie, cause, removed) => {
electronSocket.emit('webContents-session-cookies-changed' + id, [cookie, cause, removed]);
});
});
socket.on('webContents-session-cookies-get', async (id, filter, guid) => {
const browserWindow = getWindowById(id);
const cookies = await browserWindow.webContents.session.cookies.get(filter);
electronSocket.emit('webContents-session-cookies-get-completed' + guid, cookies);
});
socket.on('webContents-session-cookies-set', async (id, details, guid) => {
const browserWindow = getWindowById(id);
await browserWindow.webContents.session.cookies.set(details);
electronSocket.emit('webContents-session-cookies-set-completed' + guid);
});
socket.on('webContents-session-cookies-remove', async (id, url, name, guid) => {
const browserWindow = getWindowById(id);
await browserWindow.webContents.session.cookies.remove(url, name);
electronSocket.emit('webContents-session-cookies-remove-completed' + guid);
});
socket.on('webContents-session-cookies-flushStore', async (id, guid) => {
const browserWindow = getWindowById(id);
await browserWindow.webContents.session.cookies.flushStore();
electronSocket.emit('webContents-session-cookies-flushStore-completed' + guid);
});
socket.on('webContents-loadURL', (id, url, options) => {
const browserWindow = getWindowById(id);
browserWindow.webContents.loadURL(url, options).then(() => {

File diff suppressed because one or more lines are too long

View File

@@ -174,6 +174,43 @@ export = (socket: SocketIO.Socket) => {
browserWindow.webContents.session.setUserAgent(userAgent, acceptLanguages);
});
socket.on('register-webContents-session-cookies-changed', (id) => {
const browserWindow = getWindowById(id);
browserWindow.webContents.session.cookies.removeAllListeners('changed');
browserWindow.webContents.session.cookies.on('changed', (event, cookie, cause, removed) => {
electronSocket.emit('webContents-session-cookies-changed' + id, [cookie, cause, removed]);
});
});
socket.on('webContents-session-cookies-get', async (id, filter, guid) => {
const browserWindow = getWindowById(id);
const cookies = await browserWindow.webContents.session.cookies.get(filter);
electronSocket.emit('webContents-session-cookies-get-completed' + guid, cookies);
});
socket.on('webContents-session-cookies-set', async (id, details, guid) => {
const browserWindow = getWindowById(id);
await browserWindow.webContents.session.cookies.set(details);
electronSocket.emit('webContents-session-cookies-set-completed' + guid);
});
socket.on('webContents-session-cookies-remove', async (id, url, name, guid) => {
const browserWindow = getWindowById(id);
await browserWindow.webContents.session.cookies.remove(url, name);
electronSocket.emit('webContents-session-cookies-remove-completed' + guid);
});
socket.on('webContents-session-cookies-flushStore', async (id, guid) => {
const browserWindow = getWindowById(id);
await browserWindow.webContents.session.cookies.flushStore();
electronSocket.emit('webContents-session-cookies-flushStore-completed' + guid);
});
socket.on('webContents-loadURL', (id, url, options) => {
const browserWindow = getWindowById(id);
browserWindow.webContents.loadURL(url, options).then(() => {