Skip to content

Commit

Permalink
feat(hydrated_bloc): storagePrefix for obfuscation tolerance (#3262)
Browse files Browse the repository at this point in the history
  • Loading branch information
felangel authored Mar 9, 2022
1 parent fa22958 commit b86ddf9
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 3 deletions.
10 changes: 9 additions & 1 deletion packages/hydrated_bloc/lib/src/hydrated_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -385,9 +385,17 @@ mixin HydratedMixin<State> on BlocBase<State> {
/// in order to keep the caches independent of each other.
String get id => '';

/// Storage prefix which can be overridden to provide a custom
/// storage namespace.
/// Defaults to [runtimeType] but should be overridden in cases
/// where stored data should be resilient to obfuscation or persist
/// between debug/release builds.
String get storagePrefix => runtimeType.toString();

/// `storageToken` is used as registration token for hydrated storage.
/// Composed of [storagePrefix] and [id].
@nonVirtual
String get storageToken => '${runtimeType.toString()}$id';
String get storageToken => '$storagePrefix$id';

/// [clear] is used to wipe or invalidate the cache of a [HydratedBloc].
/// Calling [clear] will delete the cached state of the bloc
Expand Down
32 changes: 31 additions & 1 deletion packages/hydrated_bloc/test/hydrated_bloc_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,17 @@ class MyCallbackHydratedBloc extends HydratedBloc<CounterEvent, int> {
}

class MyHydratedBloc extends HydratedBloc<int, int> {
MyHydratedBloc([this._id]) : super(0);
MyHydratedBloc([this._id, this._storagePrefix]) : super(0);

final String? _id;
final String? _storagePrefix;

@override
String get id => _id ?? '';

@override
String get storagePrefix => _storagePrefix ?? super.storagePrefix;

@override
Map<String, int>? toJson(int state) {
return {'value': state};
Expand Down Expand Up @@ -142,6 +146,32 @@ void main() {
}, storage: storage);
});

test(
'reads from storage once upon initialization w/custom storagePrefix/id',
() {
HydratedBlocOverrides.runZoned(() {
const storagePrefix = '__storagePrefix__';
const id = '__id__';
MyHydratedBloc(id, storagePrefix);
verify<dynamic>(() => storage.read('$storagePrefix$id')).called(1);
}, storage: storage);
});

test('writes to storage when onChange is called w/custom storagePrefix/id',
() {
HydratedBlocOverrides.runZoned(() {
const change = Change(
currentState: 0,
nextState: 0,
);
const expected = <String, int>{'value': 0};
const storagePrefix = '__storagePrefix__';
const id = '__id__';
MyHydratedBloc(id, storagePrefix).onChange(change);
verify(() => storage.write('$storagePrefix$id', expected)).called(2);
}, storage: storage);
});

test(
'does not read from storage on subsequent state changes '
'when cache value exists', () async {
Expand Down
36 changes: 35 additions & 1 deletion packages/hydrated_bloc/test/hydrated_cubit_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,22 @@ class MyCallbackHydratedCubit extends HydratedCubit<int> {
}

class MyHydratedCubit extends HydratedCubit<int> {
MyHydratedCubit([this._id, this._callSuper = true]) : super(0);
MyHydratedCubit([
this._id,
this._callSuper = true,
this._storagePrefix,
]) : super(0);

final String? _id;
final bool _callSuper;
final String? _storagePrefix;

@override
String get id => _id ?? '';

@override
String get storagePrefix => _storagePrefix ?? super.storagePrefix;

@override
Map<String, int> toJson(int state) => {'value': state};

Expand Down Expand Up @@ -97,6 +105,32 @@ void main() {
}, storage: storage);
});

test(
'reads from storage once upon initialization w/custom storagePrefix/id',
() {
HydratedBlocOverrides.runZoned(() {
const storagePrefix = '__storagePrefix__';
const id = '__id__';
MyHydratedCubit(id, true, storagePrefix);
verify<dynamic>(() => storage.read('$storagePrefix$id')).called(1);
}, storage: storage);
});

test('writes to storage when onChange is called w/custom storagePrefix/id',
() {
HydratedBlocOverrides.runZoned(() {
const change = Change(
currentState: 0,
nextState: 0,
);
const expected = <String, int>{'value': 0};
const storagePrefix = '__storagePrefix__';
const id = '__id__';
MyHydratedCubit(id, true, storagePrefix).onChange(change);
verify(() => storage.write('$storagePrefix$id', expected)).called(2);
}, storage: storage);
});

test(
'does not read from storage on subsequent state changes '
'when cache value exists', () {
Expand Down

0 comments on commit b86ddf9

Please sign in to comment.