Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# Webview Cookie Manager
[![pub package](https://img.shields.io/pub/v/webview_cookie_manager.svg)](https://pub.dartlang.org/packages/webview_cookie_manager)

A flutter library to manager your web cookies for Android (API level 9+) and iOS (11+).
A flutter library to manager your web cookies for Android (API level 9+), iOS (11+), and macOS (10.13+).

The cookies stores and retrieves using the [httpCookieStore](https://developer.apple.com/documentation/webkit/wkwebsitedatastore/2881956-httpcookiestore) for iOS and [CookieManager](https://developer.android.com/reference/android/webkit/CookieManager) for Android.
The cookies stores and retrieves using the [httpCookieStore](https://developer.apple.com/documentation/webkit/wkwebsitedatastore/2881956-httpcookiestore) for iOS and macOS, and [CookieManager](https://developer.android.com/reference/android/webkit/CookieManager) for Android.

## Get started iOS
Set minimum version for iOS to 11.0

## Get started macOS
Set minimum version for macOS to 10.14 (Note: While the plugin supports macOS 10.13, Flutter requires macOS 10.14 as minimum deployment target)

## Usage
The WebCookieManager can be used directly or together with [webview_flutter](https://pub.dev/packages/webview_flutter).

Expand Down Expand Up @@ -70,3 +73,14 @@ target 'Runner' do
..........
end
```

## Troubleshooting on macOS
1) Set minimum target macOS version to 10.13
2) Ensure that PodFile has the use_frameworks flag
```
target 'Runner' do
use_frameworks!
use_modular_headers!
..........
end
```
202 changes: 202 additions & 0 deletions darwin/Classes/SwiftWebviewCookieManagerPlugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
#if os(iOS)
import Flutter
import UIKit
#else
import FlutterMacOS
#endif

import WebKit

@available(iOS 11.0, macOS 10.13, *)
public class SwiftWebviewCookieManagerPlugin: NSObject, FlutterPlugin {
static var httpCookieStore: WKHTTPCookieStore?

public static func register(with registrar: FlutterPluginRegistrar) {
httpCookieStore = WKWebsiteDataStore.default().httpCookieStore

#if os(iOS)
let channel = FlutterMethodChannel(name: "webview_cookie_manager", binaryMessenger: registrar.messenger())
#else
let channel = FlutterMethodChannel(name: "webview_cookie_manager", binaryMessenger: registrar.messenger)
#endif

let instance = SwiftWebviewCookieManagerPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}

public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getCookies":
guard let arguments = call.arguments as? NSDictionary,
let url = arguments["url"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "Invalid arguments for getCookies", details: nil))
return
}
SwiftWebviewCookieManagerPlugin.getCookies(urlString: url, result: result)

case "setCookies":
guard let cookies = call.arguments as? Array<NSDictionary> else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "Invalid arguments for setCookies", details: nil))
return
}
SwiftWebviewCookieManagerPlugin.setCookies(cookies: cookies, result: result)

case "hasCookies":
SwiftWebviewCookieManagerPlugin.hasCookies(result: result)

case "clearCookies":
SwiftWebviewCookieManagerPlugin.clearCookies(result: result)

default:
result(FlutterMethodNotImplemented)
}
}

public static func setCookies(cookies: Array<NSDictionary>, result: @escaping FlutterResult) {
guard let store = httpCookieStore else {
result(FlutterError(code: "COOKIE_STORE_UNAVAILABLE", message: "HTTP cookie store is not available", details: nil))
return
}

for cookie in cookies {
_setCookie(cookie: cookie, store: store)
}
result(true)
}

public static func clearCookies(result: @escaping FlutterResult) {
guard let store = httpCookieStore else {
result(FlutterError(code: "COOKIE_STORE_UNAVAILABLE", message: "HTTP cookie store is not available", details: nil))
return
}

store.getAllCookies { (cookies) in
for cookie in cookies {
store.delete(cookie, completionHandler: nil)
}

// delete HTTPCookieStorage all cookies
if let cookies = HTTPCookieStorage.shared.cookies {
for cookie in cookies {
HTTPCookieStorage.shared.deleteCookie(cookie)
}
}
result(nil)
}
}

public static func hasCookies(result: @escaping FlutterResult) {
guard let store = httpCookieStore else {
result(FlutterError(code: "COOKIE_STORE_UNAVAILABLE", message: "HTTP cookie store is not available", details: nil))
return
}

store.getAllCookies { (cookies) in
var isEmpty = cookies.isEmpty
if isEmpty {
// If it is empty, check whether the HTTPCookieStorage cookie is also empty.
isEmpty = HTTPCookieStorage.shared.cookies?.isEmpty ?? true
}
result(!isEmpty)
}
}

private static func _setCookie(cookie: NSDictionary, store: WKHTTPCookieStore) {
guard let name = cookie["name"] as? String,
let value = cookie["value"] as? String else {
print("Error: Cookie must have 'name' and 'value' properties")
return
}

let domain = cookie["domain"] as? String
let expiresDate = cookie["expires"] as? Double
let isSecure = cookie["secure"] as? Bool
let isHttpOnly = cookie["httpOnly"] as? Bool
let origin = cookie["origin"] as? String

var properties: [HTTPCookiePropertyKey: Any] = [:]
properties[.name] = name
properties[.value] = value
properties[.path] = cookie["path"] as? String ?? "/"

if let domain = domain {
properties[.domain] = domain
}
if let origin = origin {
properties[.originURL] = origin
}
if let expiresDate = expiresDate {
properties[.expires] = Date(timeIntervalSince1970: expiresDate)
}
if let isSecure = isSecure, isSecure {
properties[.secure] = "TRUE"
}
if let isHttpOnly = isHttpOnly, isHttpOnly {
properties[.init("HttpOnly")] = "YES"
}

if let httpCookie = HTTPCookie(properties: properties) {
store.setCookie(httpCookie)
} else {
print("Error: Failed to create cookie with properties: \(properties)")
}
}

public static func getCookies(urlString: String?, result: @escaping FlutterResult) {
guard let store = httpCookieStore else {
result(FlutterError(code: "COOKIE_STORE_UNAVAILABLE", message: "HTTP cookie store is not available", details: nil))
return
}

// map empty string and nil to "", indicating that no filter should be applied
let url = urlString.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } ?? ""

// ensure passed in url is parseable, and extract the host
let host = URL(string: url)?.host

// fetch and filter cookies from WKHTTPCookieStore
store.getAllCookies { (wkCookies) in

func matches(cookie: HTTPCookie) -> Bool {
// nil host means unparseable url or empty string
let containsHost = host.map { cookie.domain.contains($0) } ?? false
let containsDomain = host?.contains(cookie.domain) ?? false
return url == "" || containsHost || containsDomain
}

var cookies = wkCookies.filter { matches(cookie: $0) }

// If the cookie value is empty in WKHTTPCookieStore,
// get the cookie value from HTTPCookieStorage
if cookies.count == 0 {
if let httpCookies = HTTPCookieStorage.shared.cookies {
cookies = httpCookies.filter { matches(cookie: $0) }
}
}

let cookieList: NSMutableArray = NSMutableArray()
cookies.forEach { cookie in
cookieList.add(_cookieToDictionary(cookie: cookie))
}
result(cookieList)
}
}

public static func _cookieToDictionary(cookie: HTTPCookie) -> NSDictionary {
let result: NSMutableDictionary = NSMutableDictionary()

result.setValue(cookie.name, forKey: "name")
result.setValue(cookie.value, forKey: "value")
result.setValue(cookie.domain, forKey: "domain")
result.setValue(cookie.path, forKey: "path")
result.setValue(cookie.isSecure, forKey: "secure")
result.setValue(cookie.isHTTPOnly, forKey: "httpOnly")

if let expiresDate = cookie.expiresDate {
let expiredDate = expiresDate.timeIntervalSince1970
result.setValue(Int(expiredDate), forKey: "expires")
}

return result
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#if TARGET_OS_OSX
#import <FlutterMacOS/FlutterMacOS.h>
#else
#import <Flutter/Flutter.h>
#endif

@interface WebviewCookieManagerPlugin : NSObject<FlutterPlugin>
@end
28 changes: 28 additions & 0 deletions darwin/webview_cookie_manager.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint webview_cookie_manager.podspec` to validate before publishing.
#
Pod::Spec.new do |s|
s.name = 'webview_cookie_manager'
s.version = '0.0.1'
s.summary = 'A Flutter plugin for managing cookies in WebView.'
s.description = <<-DESC
A Flutter plugin for managing cookies in WebView on iOS and macOS platforms.
DESC
s.homepage = 'https://github.com/fryette/webview_cookie_manager'
s.license = { :file => '../LICENSE' }
s.author = { 'webview_cookie_manager' => 'https://github.com/fryette/webview_cookie_manager' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'

# Platform support
s.ios.deployment_target = '11.0'
s.osx.deployment_target = '10.13'

# Dependencies - Flutter tools will resolve the correct dependency based on platform
s.ios.dependency 'Flutter'
s.osx.dependency 'FlutterMacOS'

s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
s.swift_version = '5.0'
end
24 changes: 22 additions & 2 deletions example/.metadata
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,27 @@
# This file should be version controlled and should not be manually edited.

version:
revision: 8fe7655ed20ffd1395f68e30539a847a01a30351
channel: beta
revision: "edada7c56edf4a183c1735310e123c7f923584f1"
channel: "stable"

project_type: app

# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: edada7c56edf4a183c1735310e123c7f923584f1
base_revision: edada7c56edf4a183c1735310e123c7f923584f1
- platform: macos
create_revision: edada7c56edf4a183c1735310e123c7f923584f1
base_revision: edada7c56edf4a183c1735310e123c7f923584f1

# User provided section

# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
2 changes: 1 addition & 1 deletion example/ios/Flutter/AppFrameworkInfo.plist
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
<string>12.0</string>
</dict>
</plist>
2 changes: 1 addition & 1 deletion example/ios/Podfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
# platform :ios, '12.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
Expand Down
Loading