Skip to content

Commit

Permalink
Facebook iOS SDK 4.33
Browse files Browse the repository at this point in the history
  • Loading branch information
fbtaoge committed May 1, 2018
2 parents dd76aff + 55c8784 commit 81f00fe
Show file tree
Hide file tree
Showing 48 changed files with 1,140 additions and 111 deletions.
2 changes: 1 addition & 1 deletion Configurations/Version.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// The versions for FBSDK and Messenger SDK.
FBSDK_PROJECT_VERSION=4.32.0
FBSDK_PROJECT_VERSION=4.33.0
MNSDK_PROJECT_VERSION=TODO_SUPPORT_MNSDK

4 changes: 2 additions & 2 deletions FBSDKCoreKit.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Pod::Spec.new do |s|

s.name = "FBSDKCoreKit"
s.version = "4.32.0"
s.version = "4.33.0"
s.summary = "Official Facebook SDK for iOS to access Facebook Platform's core features"

s.description = <<-DESC
Expand All @@ -22,7 +22,7 @@ Pod::Spec.new do |s|
s.tvos.deployment_target = '9.0'

s.source = { :git => "https://github.com/facebook/facebook-ios-sdk.git",
:tag => "sdk-version-4.32.0"
:tag => "sdk-version-4.33.0"
}

s.ios.weak_frameworks = 'Accounts', 'CoreLocation', 'Social', 'Security', 'QuartzCore', 'CoreGraphics', 'UIKit', 'Foundation', 'AudioToolbox'
Expand Down
20 changes: 20 additions & 0 deletions FBSDKCoreKit/FBSDKCoreKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@
/* End PBXAggregateTarget section */

/* Begin PBXBuildFile section */
033429B220894D4700C94913 /* FBSDKAccessTokenExpirer.h in Headers */ = {isa = PBXBuildFile; fileRef = 033429B020894D4700C94913 /* FBSDKAccessTokenExpirer.h */; };
033429B320894D4700C94913 /* FBSDKAccessTokenExpirer.m in Sources */ = {isa = PBXBuildFile; fileRef = 033429B120894D4700C94913 /* FBSDKAccessTokenExpirer.m */; };
033429C8208951E300C94913 /* FBSDKAccessTokenExpirer.h in Headers */ = {isa = PBXBuildFile; fileRef = 033429B020894D4700C94913 /* FBSDKAccessTokenExpirer.h */; };
033429C9208951E400C94913 /* FBSDKAccessTokenExpirer.h in Headers */ = {isa = PBXBuildFile; fileRef = 033429B020894D4700C94913 /* FBSDKAccessTokenExpirer.h */; };
033429CA208951E400C94913 /* FBSDKAccessTokenExpirer.h in Headers */ = {isa = PBXBuildFile; fileRef = 033429B020894D4700C94913 /* FBSDKAccessTokenExpirer.h */; };
0384CEBB208E60660013D404 /* FBSDKAccessTokenExpirer.m in Sources */ = {isa = PBXBuildFile; fileRef = 033429B120894D4700C94913 /* FBSDKAccessTokenExpirer.m */; };
0384CED0208E606A0013D404 /* FBSDKAccessTokenExpirer.m in Sources */ = {isa = PBXBuildFile; fileRef = 033429B120894D4700C94913 /* FBSDKAccessTokenExpirer.m */; };
0384CED1208E606B0013D404 /* FBSDKAccessTokenExpirer.m in Sources */ = {isa = PBXBuildFile; fileRef = 033429B120894D4700C94913 /* FBSDKAccessTokenExpirer.m */; };
2A3DA4161CB4C0F600339BD4 /* FBSDKAppLinkUtilityTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A3DA4151CB4C0F600339BD4 /* FBSDKAppLinkUtilityTests.m */; };
4AF47CF31F42468E00A57A67 /* FBSDKDeviceUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AF47CF11F42468D00A57A67 /* FBSDKDeviceUtilities.m */; };
4AF47CF41F42468E00A57A67 /* FBSDKDeviceUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AF47CF11F42468D00A57A67 /* FBSDKDeviceUtilities.m */; };
Expand Down Expand Up @@ -835,6 +843,8 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
033429B020894D4700C94913 /* FBSDKAccessTokenExpirer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBSDKAccessTokenExpirer.h; sourceTree = "<group>"; };
033429B120894D4700C94913 /* FBSDKAccessTokenExpirer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBSDKAccessTokenExpirer.m; sourceTree = "<group>"; };
2A3DA4151CB4C0F600339BD4 /* FBSDKAppLinkUtilityTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBSDKAppLinkUtilityTests.m; sourceTree = "<group>"; };
4AF47CF11F42468D00A57A67 /* FBSDKDeviceUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBSDKDeviceUtilities.m; sourceTree = "<group>"; };
4AF47CF21F42468D00A57A67 /* FBSDKDeviceUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSDKDeviceUtilities.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1641,6 +1651,8 @@
9D32A83D1A69941A000A936D /* FBSDKKeychainStore.m */,
9DE1F3CB1A89D9CD00B54D98 /* FBSDKKeychainStoreViaBundleID.h */,
9DE1F3CC1A89D9CD00B54D98 /* FBSDKKeychainStoreViaBundleID.m */,
033429B020894D4700C94913 /* FBSDKAccessTokenExpirer.h */,
033429B120894D4700C94913 /* FBSDKAccessTokenExpirer.m */,
);
path = TokenCaching;
sourceTree = "<group>";
Expand Down Expand Up @@ -1717,6 +1729,7 @@
814AC82C1D1B528900D61E6C /* FBSDKTestUsersManager.h in Headers */,
814AC82D1D1B528900D61E6C /* _FBSDKTemporaryErrorRecoveryAttempter.h in Headers */,
814AC82E1D1B528900D61E6C /* FBSDKGraphRequestBody.h in Headers */,
033429CA208951E400C94913 /* FBSDKAccessTokenExpirer.h in Headers */,
814AC82F1D1B528900D61E6C /* FBSDKURLConnection.h in Headers */,
814AC8301D1B528900D61E6C /* FBSDKGraphRequestDataAttachment.h in Headers */,
814AC8311D1B528900D61E6C /* FBSDKUtility.h in Headers */,
Expand Down Expand Up @@ -1813,6 +1826,7 @@
81B71D761D19C87400933E93 /* FBSDKLogger.h in Headers */,
81B71D771D19C87400933E93 /* FBSDKError.h in Headers */,
81B71D781D19C87400933E93 /* FBSDKServerConfigurationManager.h in Headers */,
033429C8208951E300C94913 /* FBSDKAccessTokenExpirer.h in Headers */,
81B71D791D19C87400933E93 /* FBSDKGraphRequestMetadata.h in Headers */,
81B71D7A1D19C87400933E93 /* FBSDKURLConnection.h in Headers */,
81B71D7B1D19C87400933E93 /* FBSDKGraphRequest+Internal.h in Headers */,
Expand Down Expand Up @@ -1909,6 +1923,7 @@
9D6999FE1A76E166003AE384 /* FBSDKLogger.h in Headers */,
894C0B611A7150AC009137EF /* FBSDKError.h in Headers */,
89830F2B1A7805D100226ABB /* FBSDKServerConfigurationManager.h in Headers */,
033429B220894D4700C94913 /* FBSDKAccessTokenExpirer.h in Headers */,
9DF2A3FF1A70572B00DFB2FD /* FBSDKGraphRequestMetadata.h in Headers */,
9DC658A81A6EE7E200B85AAF /* FBSDKURLConnection.h in Headers */,
9DC6589B1A6EE5CD00B85AAF /* FBSDKGraphRequest+Internal.h in Headers */,
Expand Down Expand Up @@ -1980,6 +1995,7 @@
9D6DEEDB1BC24295001A94ED /* FBSDKTestUsersManager.h in Headers */,
9D6DEED11BC23A93001A94ED /* _FBSDKTemporaryErrorRecoveryAttempter.h in Headers */,
9DB0FAA01BC22CF5005EB8B1 /* FBSDKGraphRequestBody.h in Headers */,
033429C9208951E400C94913 /* FBSDKAccessTokenExpirer.h in Headers */,
9DB0FAAA1BC22D8F005EB8B1 /* FBSDKURLConnection.h in Headers */,
9DB0FAA21BC22D00005EB8B1 /* FBSDKGraphRequestDataAttachment.h in Headers */,
9DB0FA961BC22BB5005EB8B1 /* FBSDKUtility.h in Headers */,
Expand Down Expand Up @@ -2428,6 +2444,7 @@
buildActionMask = 2147483647;
files = (
9DD3FEE11D62454D00D35472 /* FBSDKURLSessionTask.m in Sources */,
0384CED0208E606A0013D404 /* FBSDKAccessTokenExpirer.m in Sources */,
814AC7E31D1B528900D61E6C /* FBSDKAppEventsDeviceInfo.m in Sources */,
814AC7E41D1B528900D61E6C /* FBSDKAppEventsUtility.m in Sources */,
814AC7E51D1B528900D61E6C /* FBSDKServerConfigurationManager.m in Sources */,
Expand Down Expand Up @@ -2536,6 +2553,7 @@
81B71D2C1D19C87400933E93 /* FBSDKBridgeAPIProtocolWebV1.m in Sources */,
81B71D2D1D19C87400933E93 /* FBSDKMath.m in Sources */,
81B71D2E1D19C87400933E93 /* FBSDKWebDialog.m in Sources */,
0384CEBB208E60660013D404 /* FBSDKAccessTokenExpirer.m in Sources */,
81B71D2F1D19C87400933E93 /* FBSDKColor.m in Sources */,
81B71D301D19C87400933E93 /* FBSDKAppLinkResolver.m in Sources */,
81B71D311D19C87400933E93 /* FBSDKIcon.m in Sources */,
Expand Down Expand Up @@ -2614,6 +2632,7 @@
89D4AEA01A803F1E00DB8C72 /* FBSDKBridgeAPIProtocolWebV1.m in Sources */,
893F44AD1A644744001DB0B6 /* FBSDKMath.m in Sources */,
89FB8C421A8425C4003CAE60 /* FBSDKWebDialog.m in Sources */,
033429B320894D4700C94913 /* FBSDKAccessTokenExpirer.m in Sources */,
9DBA6A311A80265A00B4DE6A /* FBSDKColor.m in Sources */,
7E5557371A8D833100344F86 /* FBSDKAppLinkResolver.m in Sources */,
891687D31AB33CA200F55364 /* FBSDKIcon.m in Sources */,
Expand Down Expand Up @@ -2664,6 +2683,7 @@
buildActionMask = 2147483647;
files = (
9DD3FECC1D62454C00D35472 /* FBSDKURLSessionTask.m in Sources */,
0384CED1208E606B0013D404 /* FBSDKAccessTokenExpirer.m in Sources */,
9D6DEEAC1BC236D9001A94ED /* FBSDKAppEventsDeviceInfo.m in Sources */,
9DB0FA8D1BC1CEF4005EB8B1 /* FBSDKAppEventsUtility.m in Sources */,
9D6DEEC91BC23A30001A94ED /* FBSDKServerConfigurationManager.m in Sources */,
Expand Down
17 changes: 17 additions & 0 deletions FBSDKCoreKit/FBSDKCoreKit/FBSDKAccessToken.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ FBSDK_EXTERN NSString *const FBSDKAccessTokenChangeOldKey;
*/
FBSDK_EXTERN NSString *const FBSDKAccessTokenChangeNewKey;

/*
A key in the notification's userInfo that will be set
if and only if the token has expired.
*/
FBSDK_EXTERN NSString *const FBSDKAccessTokenDidExpire;


/**
Represents an immutable access token for using Facebook services.
Expand Down Expand Up @@ -101,6 +107,11 @@ FBSDK_EXTERN NSString *const FBSDKAccessTokenChangeNewKey;
*/
@property (readonly, copy, nonatomic) NSString *userID;

/**
Returns whether the access token is expired by checking its expirationDate property
*/
@property (readonly, assign, nonatomic, getter = isExpired) BOOL expired;

- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;

Expand Down Expand Up @@ -150,6 +161,12 @@ NS_DESIGNATED_INITIALIZER;
*/
+ (FBSDKAccessToken *)currentAccessToken;

/**
Returns YES if currentAccessToken is not nil AND currentAccessToken is not expired
*/
+ (BOOL)currentAccessTokenIsActive;

/**
Sets the "global" access token that represents the currently logged in user.
- Parameter token: The access token to set.
Expand Down
15 changes: 14 additions & 1 deletion FBSDKCoreKit/FBSDKCoreKit/FBSDKAccessToken.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
NSString *const FBSDKAccessTokenDidChangeUserID = @"FBSDKAccessTokenDidChangeUserID";
NSString *const FBSDKAccessTokenChangeNewKey = @"FBSDKAccessToken";
NSString *const FBSDKAccessTokenChangeOldKey = @"FBSDKAccessTokenOld";
NSString *const FBSDKAccessTokenDidExpire = @"FBSDKAccessTokenDidExpire";

static FBSDKAccessToken *g_currentAccessToken;

Expand Down Expand Up @@ -70,6 +71,11 @@ - (BOOL)hasGranted:(NSString *)permission
return [self.permissions containsObject:permission];
}

- (BOOL)isExpired
{
return [self.expirationDate compare:NSDate.date] == NSOrderedAscending;
}

+ (FBSDKAccessToken *)currentAccessToken
{
return g_currentAccessToken;
Expand All @@ -81,7 +87,8 @@ + (void)setCurrentAccessToken:(FBSDKAccessToken *)token
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
[FBSDKInternalUtility dictionary:userInfo setObject:token forKey:FBSDKAccessTokenChangeNewKey];
[FBSDKInternalUtility dictionary:userInfo setObject:g_currentAccessToken forKey:FBSDKAccessTokenChangeOldKey];
if (![g_currentAccessToken.userID isEqualToString:token.userID]) {
// We set this flag also when the current Access Token was not valid, since there might be legacy code relying on it
if (![g_currentAccessToken.userID isEqualToString:token.userID] || ![self currentAccessTokenIsActive]) {
userInfo[FBSDKAccessTokenDidChangeUserID] = @YES;
}

Expand All @@ -100,6 +107,12 @@ + (void)setCurrentAccessToken:(FBSDKAccessToken *)token
}
}

+ (BOOL)currentAccessTokenIsActive
{
FBSDKAccessToken *currentAccessToken = [self currentAccessToken];
return currentAccessToken != nil && !currentAccessToken.isExpired;
}

+ (void)refreshCurrentAccessToken:(FBSDKGraphRequestHandler)completionHandler
{
if ([FBSDKAccessToken currentAccessToken]) {
Expand Down
14 changes: 13 additions & 1 deletion FBSDKCoreKit/FBSDKCoreKit/FBSDKAppEvents.m
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,10 @@ - (NSString *)appID
- (void)publishInstall
{
NSString *appID = [self appID];
if ([appID length] == 0) {
[FBSDKLogger singleShotLogEntry:FBSDKLoggingBehaviorDeveloperErrors logEntry:@"Missing [FBSDKAppEvents appID] for [FBSDKAppEvents publishInstall:]"];
return;
}
NSString *lastAttributionPingString = [NSString stringWithFormat:@"com.facebook.sdk:lastAttributionPing%@", appID];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:lastAttributionPingString]) {
Expand Down Expand Up @@ -801,9 +805,16 @@ - (void)checkPersistedEvents
- (void)flushOnMainQueue:(FBSDKAppEventsState *)appEventsState
forReason:(FBSDKAppEventsFlushReason)reason
{

if (appEventsState.events.count == 0) {
return;
}

if ([appEventsState.appID length] == 0) {
[FBSDKLogger singleShotLogEntry:FBSDKLoggingBehaviorDeveloperErrors logEntry:@"Missing [FBSDKAppEvents appEventsState.appID] for [FBSDKAppEvents flushOnMainQueue:]"];
return;
}

[FBSDKAppEventsUtility ensureOnMainThread:NSStringFromSelector(_cmd) className:NSStringFromClass([self class])];

[self fetchServerConfiguration:^(void) {
Expand Down Expand Up @@ -883,7 +894,8 @@ typedef NS_ENUM(NSUInteger, FBSDKAppEventsFlushResult) {

// We interpret a 400 coming back from FBRequestConnection as a server error due to improper data being
// sent down. Otherwise we assume no connectivity, or another condition where we could treat it as no connectivity.
flushResult = errorCode == 400 ? FlushResultServerError : FlushResultNoConnectivity;
// Adding 404 as having wrong/missing appID results in 404 and that is not a connectivity issue
flushResult = (errorCode == 400 || errorCode == 404) ? FlushResultServerError : FlushResultNoConnectivity;
}

if (flushResult == FlushResultServerError) {
Expand Down
4 changes: 2 additions & 2 deletions FBSDKCoreKit/FBSDKCoreKit/FBSDKCoreKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,5 @@
#import <FBSDKCoreKit/FBSDKDeviceViewControllerBase.h>
#endif

#define FBSDK_VERSION_STRING @"4.32.0"
#define FBSDK_TARGET_PLATFORM_VERSION @"v2.11"
#define FBSDK_VERSION_STRING @"4.33.0"
#define FBSDK_TARGET_PLATFORM_VERSION @"v3.0"
36 changes: 31 additions & 5 deletions FBSDKCoreKit/FBSDKCoreKit/FBSDKGraphRequestConnection.m
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,26 @@

static FBSDKErrorConfiguration *g_errorConfiguration;

#if !TARGET_OS_TV
static FBSDKAccessToken *_CreateExpiredAccessToken(FBSDKAccessToken *accessToken)
{
if (accessToken == nil) {
return nil;
}
if (accessToken.isExpired) {
return accessToken;
}
NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-1];
return [[FBSDKAccessToken alloc] initWithTokenString:accessToken.tokenString
permissions:accessToken.permissions.allObjects
declinedPermissions:accessToken.declinedPermissions.allObjects
appID:accessToken.appID
userID:accessToken.userID
expirationDate:expirationDate
refreshDate:expirationDate];
}
#endif

// ----------------------------------------------------------------------------
// FBSDKGraphRequestConnectionState

Expand Down Expand Up @@ -659,7 +679,7 @@ - (id)parseJSONOrOtherwise:(NSString *)utf8
error:(NSError **)error
{
id parsed = nil;
if (!(*error)) {
if (!(*error) && [utf8 isKindOfClass:[NSString class]]) {
parsed = [FBSDKInternalUtility objectForJSONString:utf8 error:error];
// if we fail parse we attempt a re-parse of a modified input to support results in the form "foo=bar", "true", etc.
// which is shouldn't be necessary since Graph API v2.1.
Expand Down Expand Up @@ -741,10 +761,16 @@ - (void)processResultBody:(NSDictionary *)body error:(NSError *)error metadata:(
};

#if !TARGET_OS_TV
void (^clearToken)(void) = ^{
if (!(metadata.request.flags & FBSDKGraphRequestFlagDoNotInvalidateTokenOnError)) {
void (^clearToken)(NSInteger) = ^(NSInteger errorSubcode){
if (metadata.request.flags & FBSDKGraphRequestFlagDoNotInvalidateTokenOnError) {
return;
}
if (errorSubcode == 493) {
[FBSDKAccessToken setCurrentAccessToken:_CreateExpiredAccessToken([FBSDKAccessToken currentAccessToken])];
} else {
[FBSDKAccessToken setCurrentAccessToken:nil];
}

};

FBSDKSystemAccountStoreAdapter *adapter = [FBSDKSystemAccountStoreAdapter sharedInstance];
Expand All @@ -769,14 +795,14 @@ - (void)processResultBody:(NSDictionary *)body error:(NSError *)error metadata:(
[adapter renewSystemAuthorization:^(ACAccountCredentialRenewResult result, NSError *renewError) {
NSOperationQueue *queue = _delegateQueue ?: [NSOperationQueue mainQueue];
[queue addOperationWithBlock:^{
clearToken();
clearToken(errorSubcode);
finishAndInvokeCompletionHandler();
}];
}];
return;
}
}
clearToken();
clearToken(errorSubcode);
} else if (errorCode >= 200 && errorCode < 300) {
// permission error
[adapter renewSystemAuthorization:^(ACAccountCredentialRenewResult result, NSError *renewError) {
Expand Down
3 changes: 3 additions & 0 deletions FBSDKCoreKit/FBSDKCoreKit/FBSDKSettings.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#import "FBSDKSettings+Internal.h"

#import "FBSDKAccessTokenCache.h"
#import "FBSDKAccessTokenExpirer.h"
#import "FBSDKCoreKit.h"

#define FBSDKSETTINGS_PLIST_CONFIGURATION_SETTING_IMPL(TYPE, PLIST_KEY, GETTER, SETTER, DEFAULT_VALUE) \
Expand Down Expand Up @@ -52,13 +53,15 @@ + (void)SETTER:(TYPE *)value { \
static BOOL g_disableErrorRecovery;
static NSString *g_userAgentSuffix;
static NSString *g_defaultGraphAPIVersion;
static FBSDKAccessTokenExpirer *g_accessTokenExpirer;

@implementation FBSDKSettings

+ (void)initialize
{
if (self == [FBSDKSettings class]) {
g_tokenCache = [[FBSDKAccessTokenCache alloc] init];
g_accessTokenExpirer = [[FBSDKAccessTokenExpirer alloc] init];
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#import <Foundation/Foundation.h>

@interface FBSDKAccessTokenExpirer : NSObject

@end
Loading

0 comments on commit 81f00fe

Please sign in to comment.