diff --git a/.gitignore b/.gitignore
index 3cceda5..5ed2e6d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@
# Avoid committing pubspec.lock for library packages; see
# https://dart.dev/guides/libraries/private-files#pubspeclock.
pubspec.lock
+.idea
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 26d3352..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml
deleted file mode 100644
index a2c1ccc..0000000
--- a/.idea/libraries/Dart_Packages.xml
+++ /dev/null
@@ -1,444 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/libraries/Dart_SDK.xml b/.idea/libraries/Dart_SDK.xml
deleted file mode 100644
index 3c407c5..0000000
--- a/.idea/libraries/Dart_SDK.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 639900d..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 4f2d084..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/README.md b/README.md
index 5fb5c48..3c161bc 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ Some parts of the PkPass specification are either not yet implemented, or not pl
- `signature`: The detached PKCS #7 signature using Apple certificates of the manifest. Note: Checksums _are_ checked. -
Not planned, feel free to contribute.
- `nfc`: Card payment information for Apple Pay. - Not planned, feel free to contribute.
-- `webService`: Information used to update passes using the web service. - Planned, feel free to contribute.
+- `webService`: Only pull to refresh supported. Push service not implemented yet. - Planned, feel free to contribute.
## Localizations
diff --git a/lib/pkpass.dart b/lib/pkpass.dart
index c5ffa37..7a56dd9 100644
--- a/lib/pkpass.dart
+++ b/lib/pkpass.dart
@@ -3,11 +3,11 @@
/// More dartdocs go here.
library pkpass;
-export 'src/error.dart';
-export 'src/models/barcode.dart';
-export 'src/models/beacon.dart';
-export 'src/models/location.dart';
-export 'src/models/pass.dart';
-export 'src/models/pass_structure_dictionary.dart';
-export 'src/models/pass_web_service.dart';
-export 'src/pass_file.dart';
+export 'pkpass/error.dart';
+export 'pkpass/models/barcode.dart';
+export 'pkpass/models/beacon.dart';
+export 'pkpass/models/location.dart';
+export 'pkpass/models/pass.dart';
+export 'pkpass/models/pass_structure_dictionary.dart';
+export 'pkpass/models/pass_web_service.dart';
+export 'pkpass/pass_file.dart';
diff --git a/lib/src/error.dart b/lib/pkpass/error.dart
similarity index 100%
rename from lib/src/error.dart
rename to lib/pkpass/error.dart
diff --git a/lib/src/models/barcode.dart b/lib/pkpass/models/barcode.dart
similarity index 100%
rename from lib/src/models/barcode.dart
rename to lib/pkpass/models/barcode.dart
diff --git a/lib/src/models/beacon.dart b/lib/pkpass/models/beacon.dart
similarity index 100%
rename from lib/src/models/beacon.dart
rename to lib/pkpass/models/beacon.dart
diff --git a/lib/src/models/location.dart b/lib/pkpass/models/location.dart
similarity index 100%
rename from lib/src/models/location.dart
rename to lib/pkpass/models/location.dart
diff --git a/lib/src/models/pass.dart b/lib/pkpass/models/pass.dart
similarity index 99%
rename from lib/src/models/pass.dart
rename to lib/pkpass/models/pass.dart
index 6e94f82..7c2fb38 100644
--- a/lib/src/models/pass.dart
+++ b/lib/pkpass/models/pass.dart
@@ -1,7 +1,7 @@
import 'package:intl/locale.dart';
import 'package:pkpass/pkpass.dart';
-import 'package:pkpass/src/utils/mabe_decode.dart';
+import 'package:pkpass/pkpass/utils/mabe_decode.dart';
/// Information that is required for all passes.
class PassMetadata {
diff --git a/lib/src/models/pass_structure_dictionary.dart b/lib/pkpass/models/pass_structure_dictionary.dart
similarity index 99%
rename from lib/src/models/pass_structure_dictionary.dart
rename to lib/pkpass/models/pass_structure_dictionary.dart
index 6cd3693..2ffe1e6 100644
--- a/lib/src/models/pass_structure_dictionary.dart
+++ b/lib/pkpass/models/pass_structure_dictionary.dart
@@ -1,7 +1,7 @@
import 'package:intl/locale.dart';
import 'package:pkpass/pkpass.dart';
-import 'package:pkpass/src/utils/mabe_decode.dart';
+import 'package:pkpass/pkpass/utils/mabe_decode.dart';
/// Keys that define the structure of the pass.
///
diff --git a/lib/src/models/pass_web_service.dart b/lib/pkpass/models/pass_web_service.dart
similarity index 95%
rename from lib/src/models/pass_web_service.dart
rename to lib/pkpass/models/pass_web_service.dart
index bf6472f..52c664c 100644
--- a/lib/src/models/pass_web_service.dart
+++ b/lib/pkpass/models/pass_web_service.dart
@@ -1,5 +1,3 @@
-/// TODO: implement PassKit Web Service Reference
-///
/// Metadata required for Pass Web Service
///
/// https://developer.apple.com/library/archive/documentation/PassKit/Reference/PassKit_WebService/WebService.html#//apple_ref/doc/uid/TP40011988
diff --git a/lib/src/pass_file.dart b/lib/pkpass/pass_file.dart
similarity index 97%
rename from lib/src/pass_file.dart
rename to lib/pkpass/pass_file.dart
index acaa061..86df73e 100644
--- a/lib/src/pass_file.dart
+++ b/lib/pkpass/pass_file.dart
@@ -6,8 +6,8 @@ import 'package:crypto/crypto.dart';
import 'package:intl/locale.dart';
import 'package:pkpass/pkpass.dart';
-import 'package:pkpass/src/utils/file_matcher.dart';
-import 'package:pkpass/src/utils/lproj_parser.dart';
+import 'package:pkpass/pkpass/utils/file_matcher.dart';
+import 'package:pkpass/pkpass/utils/lproj_parser.dart';
final _utf8codec = Utf8Codec();
final _jsonCodec = JsonCodec();
diff --git a/lib/src/utils/color_helper.dart b/lib/pkpass/utils/color_helper.dart
similarity index 100%
rename from lib/src/utils/color_helper.dart
rename to lib/pkpass/utils/color_helper.dart
diff --git a/lib/src/utils/file_matcher.dart b/lib/pkpass/utils/file_matcher.dart
similarity index 100%
rename from lib/src/utils/file_matcher.dart
rename to lib/pkpass/utils/file_matcher.dart
diff --git a/lib/src/utils/lproj_parser.dart b/lib/pkpass/utils/lproj_parser.dart
similarity index 100%
rename from lib/src/utils/lproj_parser.dart
rename to lib/pkpass/utils/lproj_parser.dart
diff --git a/lib/src/utils/mabe_decode.dart b/lib/pkpass/utils/mabe_decode.dart
similarity index 100%
rename from lib/src/utils/mabe_decode.dart
rename to lib/pkpass/utils/mabe_decode.dart
diff --git a/lib/pkpass_web_service.dart b/lib/pkpass_web_service.dart
new file mode 100644
index 0000000..a1d5ff2
--- /dev/null
+++ b/lib/pkpass_web_service.dart
@@ -0,0 +1,9 @@
+/// The PkPass Web Service.
+///
+/// So far only supports update check but no push service.
+///
+/// https://developer.apple.com/library/archive/documentation/PassKit/Reference/PassKit_WebService/WebService.html#//apple_ref/doc/uid/TP40011988
+library pkpass_web_service;
+
+export 'pkpass_web_wervice/web_service.dart';
+export 'pkpass_web_wervice/web_service_error.dart';
diff --git a/lib/pkpass_web_wervice/utils/http_date.dart b/lib/pkpass_web_wervice/utils/http_date.dart
new file mode 100644
index 0000000..78615f1
--- /dev/null
+++ b/lib/pkpass_web_wervice/utils/http_date.dart
@@ -0,0 +1,219 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// copied from dart:io
+
+/// Utility functions for working with dates with HTTP specific date
+/// formats.
+class HttpDate {
+ // From RFC-2616 section "3.3.1 Full Date",
+ // http://tools.ietf.org/html/rfc2616#section-3.3.1
+ //
+ // HTTP-date = rfc1123-date | rfc850-date | asctime-date
+ // rfc1123-date = wkday "," SP date1 SP time SP "GMT"
+ // rfc850-date = weekday "," SP date2 SP time SP "GMT"
+ // asctime-date = wkday SP date3 SP time SP 4DIGIT
+ // date1 = 2DIGIT SP month SP 4DIGIT
+ // ; day month year (e.g., 02 Jun 1982)
+ // date2 = 2DIGIT "-" month "-" 2DIGIT
+ // ; day-month-year (e.g., 02-Jun-82)
+ // date3 = month SP ( 2DIGIT | ( SP 1DIGIT ))
+ // ; month day (e.g., Jun 2)
+ // time = 2DIGIT ":" 2DIGIT ":" 2DIGIT
+ // ; 00:00:00 - 23:59:59
+ // wkday = "Mon" | "Tue" | "Wed"
+ // | "Thu" | "Fri" | "Sat" | "Sun"
+ // weekday = "Monday" | "Tuesday" | "Wednesday"
+ // | "Thursday" | "Friday" | "Saturday" | "Sunday"
+ // month = "Jan" | "Feb" | "Mar" | "Apr"
+ // | "May" | "Jun" | "Jul" | "Aug"
+ // | "Sep" | "Oct" | "Nov" | "Dec"
+
+ /// Format a date according to
+ /// [RFC-1123](http://tools.ietf.org/html/rfc1123 "RFC-1123"),
+ /// e.g. `Thu, 1 Jan 1970 00:00:00 GMT`.
+ static String format(DateTime date) {
+ const List wkday = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
+ const List month = [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+ ];
+
+ DateTime d = date.toUtc();
+ StringBuffer sb = StringBuffer()
+ ..write(wkday[d.weekday - 1])
+ ..write(", ")
+ ..write(d.day <= 9 ? "0" : "")
+ ..write(d.day.toString())
+ ..write(" ")
+ ..write(month[d.month - 1])
+ ..write(" ")
+ ..write(d.year.toString())
+ ..write(d.hour <= 9 ? " 0" : " ")
+ ..write(d.hour.toString())
+ ..write(d.minute <= 9 ? ":0" : ":")
+ ..write(d.minute.toString())
+ ..write(d.second <= 9 ? ":0" : ":")
+ ..write(d.second.toString())
+ ..write(" GMT");
+ return sb.toString();
+ }
+
+ /// Parse a date string in either of the formats
+ /// [RFC-1123](http://tools.ietf.org/html/rfc1123 "RFC-1123"),
+ /// [RFC-850](http://tools.ietf.org/html/rfc850 "RFC-850") or
+ /// ANSI C's asctime() format. These formats are listed here.
+ ///
+ /// Thu, 1 Jan 1970 00:00:00 GMT
+ /// Thursday, 1-Jan-1970 00:00:00 GMT
+ /// Thu Jan 1 00:00:00 1970
+ ///
+ /// For more information see [RFC-2616 section
+ /// 3.1.1](http://tools.ietf.org/html/rfc2616#section-3.3.1
+ /// "RFC-2616 section 3.1.1").
+ static DateTime parse(String date) {
+ final int sp = 32;
+ const List wkdays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
+ const List weekdays = [
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday",
+ ];
+ const List months = [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+ ];
+
+ final int formatRfc1123 = 0;
+ final int formatRfc850 = 1;
+ final int formatAsctime = 2;
+
+ int index = 0;
+ String tmp;
+
+ void expect(String s) {
+ if (date.length - index < s.length) {
+ throw Exception("Invalid HTTP date $date");
+ }
+ String tmp = date.substring(index, index + s.length);
+ if (tmp != s) {
+ throw Exception("Invalid HTTP date $date");
+ }
+ index += s.length;
+ }
+
+ int expectWeekday() {
+ int weekday;
+ // The formatting of the weekday signals the format of the date string.
+ int pos = date.indexOf(",", index);
+ if (pos == -1) {
+ int pos = date.indexOf(" ", index);
+ if (pos == -1) throw Exception("Invalid HTTP date $date");
+ tmp = date.substring(index, pos);
+ index = pos + 1;
+ weekday = wkdays.indexOf(tmp);
+ if (weekday != -1) {
+ return formatAsctime;
+ }
+ } else {
+ tmp = date.substring(index, pos);
+ index = pos + 1;
+ weekday = wkdays.indexOf(tmp);
+ if (weekday != -1) {
+ return formatRfc1123;
+ }
+ weekday = weekdays.indexOf(tmp);
+ if (weekday != -1) {
+ return formatRfc850;
+ }
+ }
+ throw Exception("Invalid HTTP date $date");
+ }
+
+ int expectMonth(String separator) {
+ int pos = date.indexOf(separator, index);
+ if (pos - index != 3) throw Exception("Invalid HTTP date $date");
+ tmp = date.substring(index, pos);
+ index = pos + 1;
+ int month = months.indexOf(tmp);
+ if (month != -1) return month;
+ throw Exception("Invalid HTTP date $date");
+ }
+
+ int expectNum(String separator) {
+ int pos;
+ if (separator.isNotEmpty) {
+ pos = date.indexOf(separator, index);
+ } else {
+ pos = date.length;
+ }
+ String tmp = date.substring(index, pos);
+ index = pos + separator.length;
+ try {
+ int value = int.parse(tmp);
+ return value;
+ } on FormatException {
+ throw Exception("Invalid HTTP date $date");
+ }
+ }
+
+ void expectEnd() {
+ if (index != date.length) {
+ throw Exception("Invalid HTTP date $date");
+ }
+ }
+
+ int format = expectWeekday();
+ int year;
+ int month;
+ int day;
+ int hours;
+ int minutes;
+ int seconds;
+ if (format == formatAsctime) {
+ month = expectMonth(" ");
+ if (date.codeUnitAt(index) == sp) index++;
+ day = expectNum(" ");
+ hours = expectNum(":");
+ minutes = expectNum(":");
+ seconds = expectNum(" ");
+ year = expectNum("");
+ } else {
+ expect(" ");
+ day = expectNum(format == formatRfc1123 ? " " : "-");
+ month = expectMonth(format == formatRfc1123 ? " " : "-");
+ year = expectNum(" ");
+ hours = expectNum(":");
+ minutes = expectNum(":");
+ seconds = expectNum(" ");
+ expect("GMT");
+ }
+ expectEnd();
+ return DateTime.utc(year, month + 1, day, hours, minutes, seconds, 0);
+ }
+}
diff --git a/lib/pkpass_web_wervice/web_service.dart b/lib/pkpass_web_wervice/web_service.dart
new file mode 100644
index 0000000..78eb4d0
--- /dev/null
+++ b/lib/pkpass_web_wervice/web_service.dart
@@ -0,0 +1,69 @@
+import 'dart:typed_data';
+
+import 'package:http/http.dart';
+
+import 'package:pkpass/pkpass.dart';
+import 'package:pkpass/pkpass_web_wervice/utils/http_date.dart';
+import 'web_service_error.dart';
+
+/// PassKit WebService implementation
+///
+/// A Representational State Transfer (REST)–style web service protocol is used
+/// to communicate with your server about changes to passes, and to fetch the
+/// latest version of a pass when it has changed.
+///
+/// https://developer.apple.com/library/archive/documentation/PassKit/Reference/PassKit_WebService/WebService.html#//apple_ref/doc/uid/TP40011988
+class PkPassWebService {
+ static const _apiVersion = 'v1';
+ static Client? _client;
+
+ /// The [PassMetadata] to check for updates.
+ final PassMetadata metadata;
+
+ /// An optional [Client] used for any http requests
+ final Client? client;
+
+ const PkPassWebService(this.metadata, {this.client});
+
+ PassWebService get webService {
+ final service = metadata.webService;
+ if (service == null) noWebServiceProvided();
+ return service;
+ }
+
+ Client get httpClient {
+ final client = this.client;
+ if (client != null) return client;
+ return _client ??= Client();
+ }
+
+ /// Getting the Latest Version of a Pass
+ ///
+ /// Requests the latest version of the current PkPass file
+ ///
+ /// [modifiedSince] should be provided in order to support "304 Not Modified"
+ Future getLatestVersion([DateTime? modifiedSince]) async {
+ final identifier = metadata.passTypeIdentifier;
+ final serial = metadata.serialNumber;
+ final endpoint = '/$_apiVersion/passes/$identifier/$serial';
+
+ final response = await httpClient.get(
+ Uri.parse(webService.webServiceURL.toString() + endpoint),
+ headers: {
+ if (modifiedSince != null)
+ 'If-Modified-Since': HttpDate.format(modifiedSince),
+ 'Authorization': 'ApplePass ${webService.authenticationToken}',
+ },
+ );
+ switch (response.statusCode) {
+ case 200:
+ return response.bodyBytes;
+ case 304:
+ return null;
+ default:
+ throw WebServiceResponseError(response);
+ }
+ }
+
+ Never noWebServiceProvided() => throw WebServiceUnavailable();
+}
diff --git a/lib/pkpass_web_wervice/web_service_error.dart b/lib/pkpass_web_wervice/web_service_error.dart
new file mode 100644
index 0000000..58f3b72
--- /dev/null
+++ b/lib/pkpass_web_wervice/web_service_error.dart
@@ -0,0 +1,19 @@
+import 'package:http/http.dart';
+
+import 'package:pkpass/pkpass.dart';
+
+abstract class PkPassWebServiceError extends PKPassError {
+ PkPassWebServiceError({required super.message});
+}
+
+class WebServiceUnavailable extends PkPassWebServiceError {
+ WebServiceUnavailable()
+ : super(message: 'The PkPass file does not contain any web service.');
+}
+
+class WebServiceResponseError extends PkPassWebServiceError {
+ final Response response;
+
+ WebServiceResponseError(this.response)
+ : super(message: 'Unexpected response from web service');
+}
diff --git a/test/pkpass_test.dart b/test/pkpass_test.dart
index 5031fdc..9c3c3d5 100644
--- a/test/pkpass_test.dart
+++ b/test/pkpass_test.dart
@@ -5,7 +5,7 @@ import 'package:intl/locale.dart';
import 'package:test/test.dart';
import 'package:pkpass/pkpass.dart';
-import 'package:pkpass/src/utils/file_matcher.dart';
+import 'package:pkpass/pkpass/utils/file_matcher.dart';
void main() {
final archive = [