v0.10.0-dev.3

Blossom

low level

CLI ndk blossom upload|download|delete|list|mirror|check|servers

Example

final downloadResult = await ndk.blossom.getBlob(
  sha256:
      "b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553",
  serverUrls: ["https://cdn.hzrd149.com", "https://nostr.download"],
);

print(
  "file of type: ${downloadResult.mimeType}, size: ${downloadResult.data.length}",
);

When to use

For a simpler, more generic API, check out

Files
../files/

If no servers are specified the default user server list (kind 10063) is used for upload and delete.

The auth events get automatically signed and are valid for:

const Duration BLOSSOM_AUTH_EXPIRATION = Duration(minutes: 5);

methods - Blossom

uploadBlob

upload a blob, if serverMediaOptimisation is set to true the /media endpoint is used.


/// Gets the signer to use for blossom operations
/// Priority: customSigner > logged in account signer > temporary signer
EventSigner _getSigner(EventSigner? customSigner) {
  if (customSigner != null) return customSigner;

  if (_accounts.canSign) {
    return _accounts.getLoggedAccount()!.signer;
  }

  // Create a temporary signer if no account is logged in
  final keyPair = Bip340.generatePrivateKey();
  return _eventSignerFactory.create(

getBlob

Download the blob and use fallback if the blob is not found or the server is offline.

    ["x", dataSha256],
    ["expiration", "${now + BLOSSOM_AUTH_EXPIRATION.inMilliseconds}"],
  ],
);

final signedAuthorization = await signer.sign(myAuthorization);

serverUrls ??= await _userServerList.getUserServerList(
  pubkeys: [signer.getPublicKey()],

checkBlob

final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;

final signer = _getSigner(customSigner);

String? fileHash = precomputedSha256;

if (fileHash == null) {
  // Compute file hash without loading entire file into memory
  await for (final hashProgress in _blossomImpl.computeFileHash(filePath)) {
    yield BlobUploadProgress(
      currentServer: '',
      sentBytes: hashProgress.processedBytes,

getBlobStream

Similar to getBlob, it streams the data, which is helpful for video files.

    serverUrls: serverUrls,
    authorization: signedAuthorization,
    contentType: contentType,
    strategy: strategy,
    mediaOptimisation: serverMediaOptimisation,
  );
}

/// Mirror a blob from a blossom URL to specified servers using the blossom /mirror endpoint
///

listBlobs

  targetServerUrls.map(
    (serverUrl) => _blossomImpl.mirrorToServer(
      fileUrl: blossomUrl.toString(),
      serverUrl: serverUrl,
      sha256: sha256,
      authorization: signedAuthorization,
    ),
  ),
);

return results;

deleteBlob

  throw Exception(
    "pubkeyToFetchUserServerList is null and serverUrls is null",
  );
}

serverUrls ??= await _userServerList.getUserServerList(
  pubkeys: [pubkeyToFetchUserServerList],
);

directDownload


final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
myAuthorization = Nip01Utils.createEventCalculateId(
  content: "get",

report

    tags: [
      ["t", "get"],
      ["x", sha256],
      ["expiration", "${now + BLOSSOM_AUTH_EXPIRATION.inMilliseconds}"],
    ],
  );

  signedAuthorization = await signer.sign(myAuthorization);
}

if (serverUrls == null) {
  if (pubkeyToFetchUserServerList == null) {
    throw Exception(
      "pubkeyToFetchUserServerList is null and serverUrls is null",
    );

methods - BlossomUserServerList

To get and set the user server list e.g. on settings page, you can use BlossomUserServerList

getUserServerList

/// Get user server list \
/// returns list of server urls \
/// returns null if the user has no server list
Future<List<String>?> getUserServerList({
  required List<String> pubkeys,
}) async {

publishUserServerList

  required List<String> serverUrlsOrdered,
}) async {
  if (serverUrlsOrdered.isEmpty) {
    throw Exception("serverUrlsOrdered is empty");
  }