DIMSDK 0.7.2

DIMSDK 0.7.2

John Chenmoky维护。



 
依赖项
DIMCore~> 0.7.2
DaoKeDao~> 0.7.2
MingKeMing~> 0.7.2
 

DIMSDK 0.7.2

  • Albert Moky

去中心化即时通讯(Objective-C SDK)

license Version PRs Welcome Platform

依赖项

cd GitHub/
mkdir dimchat; cd dimchat/

git clone https://github.com/dimchat/sdk-objc.git
git clone https://github.com/dimchat/core-objc.git
git clone https://github.com/dimchat/dkd-objc.git
git clone https://github.com/dimchat/mkm-objc.git

账户

用户的私钥、ID、元数据和签证文件在客户端生成,并在DIM站上只广播metadocument

注册用户账户

第一步. 生成私钥(使用非对称算法)

id<MKMPrivateKey> SK = MKMPrivateKeyGenerate(ACAlgorithmRSA);

请注意:注册后,客户应将私钥保存在安全存储中。

步骤 2. 使用私钥(和元种)生成元数据

NSString *seed = @"username";
id<MKMMeta> meta = MKMMetaGenerate(MKMMetaDefaultVersion, SK, seed);

步骤 3. 使用元数据和网络类型生成 ID

NSString *terminal = nil;
id<MKMID> ID = MKMIDGenerate(meta, MKMEntityType_User, terminal);

创建并上传用户文档

步骤 4. 使用 ID 创建文档并使用私钥签名

id<MKMVisa> doc = MKMDocumentNew(MKMDocument_Visa, ID);
// set nickname and avatar URL
[doc setName:@"Albert Moky"];
[doc setAvatar:@"https://secure.gravatar.com/avatar/34aa0026f924d017dcc7a771f495c086"];
// sign
[doc sign:SK];

步骤 5. 将元数据和文档发送到站

id<MKMID> gid = MKMEveryone();
id<MKMID> sid = MKMIDParse(@"station@anywhere");
id<DKDContent> cmd = [[DIMDocumentCommand alloc] initWithID:ID meta:meta document:doc];
[cmd setGroup:gid];
[messenger sendContent:cmd sender:nil receiver:sid priority:0];

签证文件应在连接成功并握手被接受后发送到站,详细信息将在后续章节中提供

连接和握手

步骤 1. 连接到 DIM 站(TCP)

步骤 2. 准备接收消息数据包

- (void)onReceive:(NSData *)data {
    NSData *response = [messenger onReceivePackage:data];
    if ([response length] > 0) {
        // send processing result back to the station
        [self send:response];
    }
}

步骤 3. 发送第一个 握手 命令

(1) 创建握手命令

// first handshake will have no session key
NSString *session = nil;
cmd = [[DIMHandshakeCommand alloc] initWithSessionKey:session];

(2) 打包、加密和签名

NSDate *now = nil;
id<DKDEnvelope> env = DKDEnvelopeCreate(sender, receiver, now);
id<DKDInstantMessage> iMsg = DKDInstantMessageCreate(env, cmd);
id<DKDSecureMessage>  sMsg = [messenger encryptMessage:iMsg];
id<DKDReliableMessage rMsg = [messenger signMessage:sMsg];

(3) 元协议

在第一个消息包中附加元数据是为了确保站能够找到它,特别是当用户首次连接到该站时。

if (cmd.state == DIMHandshake_Start) {
    rMsg.meta = user.meta;
}

(4) 发送序列化消息数据包

NSData *data = [messenger serializeMessage:rMsg];
[self send:data];

步骤 4. 等待握手响应

CPU(命令处理单元)将捕捉到站发送的握手命令响应,并自动处理它们,因此只需等待握手成功或网络错误。

消息

内容

  • 文本消息
id<DKDContent> content = [[DIMTextContent alloc] initWithText:@"Hey, girl!"];
  • 图片消息
content = [[DIMImageContent alloc] initWithImageData:data
                                            filename:@"image.png"];
  • 语音消息
content = [[DIMAudioContent alloc] initWithAudioData:data
                                            filename:@"voice.mp3"];
  • 视频消息
content = [[DIMVideoContent alloc] initWithVideoData:data
                                            filename:@"movie.mp4"];

注意:文件消息内容(图片、音频、视频)仅发送文件名和存储文件数据(使用相同对称密钥加密)的URL。

命令

  • 使用联系ID查询元数据
id<DKDCommand> cmd = [[DIMMetaCommand alloc] initWithID:ID];
  • 使用联系ID查询文档
id<DKDCommand> cmd = [[DIMDocumentCommand alloc] initWithID:ID];

发送命令

@implementation DIMMessenger (Extension)

- (BOOL)sendCommand:(id<DKDCommand>)cmd {
    DIMStation *server = [self currentServer];
    NSAssert(server, @"server not connected yet");
    return [self sendContent:cmd receiver:server.ID];
}

@end

仅带有ID的元命令或文档命令表示查询,CPU将自动捕获和处理所有响应。

命令处理单元

您可以将自定义命令(例如 搜索命令)发送并准备一个处理器来处理响应。

搜索命令处理器

@interface DIMSearchCommandProcessor : DIMCommandProcessor

@end
NSString * const kNotificationName_SearchUsersUpdated = @"SearchUsersUpdated";

@implementation DIMSearchCommandProcessor

- (NSArray<id<DKDContent>> *)processContent:(id<DKDContent>)content
                                withMessage:(id<DKDReliableMessage>)rMsg {
    NSAssert([content isKindOfClass:[DIMSearchCommand class]], @"search command error: %@", content);
    DIMSearchCommand *command = (DIMSearchCommand *)content;
    NSString *cmd = command.cmd;
    
    NSString *notificationName;
    if ([cmd isEqualToString:DIMCommand_Search]) {
        notificationName = kNotificationName_SearchUsersUpdated;
    } else if ([cmd isEqualToString:DIMCommand_OnlineUsers]) {
        notificationName = kNotificationName_OnlineUsersUpdated;
    } else {
        NSAssert(false, @"search command error: %@", cmd);
        return nil;
    }
    
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    [nc postNotificationName:notificationName
                      object:self
                    userInfo:content];
    // response nothing to the station
    return nil;
}

@end

握手命令处理器

@interface DIMHandshakeCommandProcessor : DIMCommandProcessor

@end
@implementation DIMHandshakeCommandProcessor

- (NSArray<id<DKDContent>> *)success {
    DIMServer *server = (DIMServer *)[self.messenger currentServer];
    [server handshakeAccepted:YES];
    return nil;
}

- (NSArray<id<DKDContent>> *)ask:(NSString *)sessionKey {
    DIMServer *server = (DIMServer *)[self.messenger currentServer];
    [server handshakeWithSession:sessionKey];
    return nil;
}

- (NSArray<id<DKDContent>> *)processContent:(id<DKDContent>)content
                                withMessage:(id<DKDReliableMessage>)rMsg {
    NSAssert([content isKindOfClass:[DIMHandshakeCommand class]], @"handshake error: %@", content);
    DIMHandshakeCommand *command = (DIMHandshakeCommand *)content;
    NSString *title = command.title;
    if ([title isEqualToString:@"DIM!"]) {
        // S -> C
        return [self success];
    } else if ([title isEqualToString:@"DIM?"]) {
        // S -> C
        return [self ask:command.sessionKey];
    } else {
        // C -> S: Hello world!
        NSAssert(false, @"handshake command error: %@", command);
        return nil;
    }
}

@end

别忘了注册它们。

@implementation DIMClientContentProcessorCreator

- (id<DIMContentProcessor>)createCommandProcessor:(NSString *)name type:(DKDContentType)msgType {
    // handshake
    if ([name isEqualToString:DIMCommand_Handshake]) {
        return CREATE_CPU(DIMHandshakeCommandProcessor);
    }
    // search
    if ([name isEqualToString:DIMCommand_Search]) {
        return CREATE_CPU(DIMSearchCommandProcessor);
    } else if ([name isEqualToString:DIMCommand_OnlineUsers]) {
        // TODO: shared the same processor with 'search'?
        return CREATE_CPU(DIMSearchCommandProcessor);
    }

    // others
    return [super createCommandProcessor:name type:msgType];
}

@end

保存即时消息

在 DIMMessenger 中覆盖 saveMessage: 接口以保存即时消息

- (BOOL)saveMessage:(id<DKDInstantMessage>)iMsg {
    id<DKDContent> content = iMsg.content;
    // TODO: check message type
    //       only save normal message and group commands
    //       ignore 'Handshake', ...
    //       return true to allow responding
    
    if ([content conformsToProtocol:@protocol(DKDHandshakeCommand)]) {
        // handshake command will be processed by CPUs
        // no need to save handshake command here
        return YES;
    }
    if ([content conformsToProtocol:@protocol(DKDMetaCommand)]) {
        // meta & document command will be checked and saved by CPUs
        // no need to save meta & document command here
        return YES;
    }
    if ([content conformsToProtocol:@protocol(DKDSearchCommand)]) {
        // search result will be parsed by CPUs
        // no need to save search command here
        return YES;
    }
    
    DIMAmanuensis *clerk = [DIMAmanuensis sharedInstance];
    
    if ([content conformsToProtocol:@protocol(DKDReceiptCommand)]) {
        return [clerk saveReceipt:iMsg];
    } else {
        return [clerk saveMessage:iMsg];
    }
}

版权所有 © 2018-2023 Albert Moky