去中心化即时通讯(Objective-C SDK)
依赖关系
- DIM Core (core-objc) 去中心化即时通讯协议
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 站台上广播 meta
& document
。
注册用户账户
步骤 1. 使用非对称算法生成私钥
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<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的MetaCommand或DocumentCommand表示查询,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